{"id":20662474,"url":"https://github.com/teloxide/dptree","last_synced_at":"2025-04-19T15:52:55.941Z","repository":{"id":41106150,"uuid":"384077669","full_name":"teloxide/dptree","owner":"teloxide","description":"Asynchronous event dispatch for Rust","archived":false,"fork":false,"pushed_at":"2023-05-26T02:01:34.000Z","size":317,"stargazers_count":118,"open_issues_count":2,"forks_count":5,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-20T00:53:55.680Z","etag":null,"topics":["asynchronous","dispatcher","rust"],"latest_commit_sha":null,"homepage":"https://docs.rs/dptree/latest/dptree/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/teloxide.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-07-08T09:51:54.000Z","updated_at":"2024-06-25T17:06:22.936Z","dependencies_parsed_at":"2024-06-25T17:06:21.168Z","dependency_job_id":"09c1ca7a-e505-48bf-863b-990d6ba33206","html_url":"https://github.com/teloxide/dptree","commit_stats":{"total_commits":201,"total_committers":4,"mean_commits":50.25,"dds":0.5174129353233831,"last_synced_commit":"da3de9d583380cfe2192f10d1d7681c3e79339a1"},"previous_names":["p0lunin/dptree"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teloxide%2Fdptree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teloxide%2Fdptree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teloxide%2Fdptree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/teloxide%2Fdptree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/teloxide","download_url":"https://codeload.github.com/teloxide/dptree/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249730684,"owners_count":21317328,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asynchronous","dispatcher","rust"],"created_at":"2024-11-16T19:14:20.594Z","updated_at":"2025-04-19T15:52:55.903Z","avatar_url":"https://github.com/teloxide.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dptree\n[![Rust](https://github.com/teloxide/dptree/actions/workflows/rust.yml/badge.svg)](https://github.com/teloxide/dptree/actions/workflows/rust.yml)\n[![Crates.io](https://img.shields.io/crates/v/dptree.svg)](https://crates.io/crates/dptree)\n[![Docs.rs](https://docs.rs/dptree/badge.svg)](https://docs.rs/dptree)\n\nAn implementation of the [chain (tree) of responsibility] pattern.\n\n[[`examples/web_server.rs`](examples/web_server.rs)]\n```rust\nuse dptree::prelude::*;\n\ntype WebHandler = Endpoint\u003c'static, String\u003e;\n\n#[rustfmt::skip]\n#[tokio::main]\nasync fn main() {\n    let web_server = dptree::entry()\n        .branch(smiles_handler())\n        .branch(sqrt_handler())\n        .branch(not_found_handler());\n\n    assert_eq!(\n        web_server.dispatch(dptree::deps![\"/smile\"]).await,\n        ControlFlow::Break(\"🙃\".to_owned())\n    );\n    assert_eq!(\n        web_server.dispatch(dptree::deps![\"/sqrt 16\"]).await,\n        ControlFlow::Break(\"4\".to_owned())\n    );\n    assert_eq!(\n        web_server.dispatch(dptree::deps![\"/lol\"]).await,\n        ControlFlow::Break(\"404 Not Found\".to_owned())\n    );\n}\n\nfn smiles_handler() -\u003e WebHandler {\n    dptree::filter(|req: \u0026'static str| req.starts_with(\"/smile\"))\n        .endpoint(|| async { \"🙃\".to_owned() })\n}\n\nfn sqrt_handler() -\u003e WebHandler {\n    dptree::filter_map(|req: \u0026'static str| {\n        if req.starts_with(\"/sqrt\") {\n            let (_, n) = req.split_once(' ')?;\n            n.parse::\u003cf64\u003e().ok()\n        } else {\n            None\n        }\n    })\n    .endpoint(|n: f64| async move { format!(\"{}\", n.sqrt()) })\n}\n\nfn not_found_handler() -\u003e WebHandler {\n    dptree::endpoint(|| async { \"404 Not Found\".to_owned() })\n}\n```\n\n## Features\n\n - ✔️ Declarative handlers: `dptree::{endpoint, filter, filter_map, ...}`.\n - ✔️ A lightweight functional design without typical OOP hodgepodge.\n - ✔️ [Dependency injection (DI)] out-of-the-box.\n - ✔️ Startup-time [type checking] of run-time dependencies via `dptree::type_check`.\n - ✔️ Handler introspection facilities.\n - ✔️ Battle-tested: dptree is used in [`teloxide`] as a framework for Telegram update dispatching.\n - ✔️ Runtime-agnostic: uses only the [`futures`] crate.\n\n[Dependency injection (DI)]: https://en.wikipedia.org/wiki/Dependency_injection\n[type checking]: https://github.com/teloxide/dptree/blob/master/examples/diagnostics.rs\n[`teloxide`]: https://github.com/teloxide/teloxide\n[`futures`]: https://github.com/rust-lang/futures-rs\n\n## Explanation\n\nThe above code is a simple web server dispatching tree. In pseudocode, it would look like this:\n\n - `dptree::entry()`: dispatch an update to the following branch handlers:\n   - `.branch(smiles_handler())`: if the update satisfies the condition (`dptree::filter`), return a smile (`.endpoint`). Otherwise, pass the update forwards.\n   - `.branch(sqrt_handler())`: if the update is a number (`dptree::filter_map`), return the square of it. Otherwise, pass the update forwards.\n   - `.branch(not_found_handler())`: return `404 Not Found` immediately.\n\n**Control flow:** as you can see, we have just described a dispatching scheme consisting of three branches. First, dptree enters the first handler `smiles_handler`, then, if it fails to process an update, it passes the update to `sqrt_handler` and so on. If nobody have succeeded in handling an update, the control flow enters `not_found_handler` that returns the error. In other words, the result of the whole `.dispatch` call would be the result of the first handler that succeeded to handle an incoming update.\n\n**Dependency injection:** instead of passing straightforward values to `.dispatch`, we use the `dptree::deps!` macro. It accepts a sequence of values and constructs `DependencyMap` out of them. The handlers request values of certain types in their signatures (such as `|req: \u0026'static str|`), and dptree automatically _injects_ the values from `dptree::deps!` into these functions. You can use `dptree::type_check` to make sure that all required types are specified in `dptree::deps!`; otherwise, `dptree` will delay run-time type checking until execution.\n\nUsing dptree, you can specify arbitrary complex dispatching schemes using the same recurring patterns you have seen above.\n\n[chain (tree) of responsibility]: https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern\n\n## Pitfalls\n\n - `DependencyMap` can panic at run-time if a non-existing dependency is requested. Use `dptree::type_check` to make sure that all required types are specified in the container.\n - `.branch` and `.chain` are different operations. See [\"The difference between chaining and branching\"](https://docs.rs/dptree/latest/dptree/struct.Handler.html#the-difference-between-chaining-and-branching).\n\n## Design choices\n\n### Functional\n\nWe decided to use a [continuation-passing style (CPS)] internally and expose neat handler patterns to library users. This is contrary to what you might have seen in typical object-oriented models of the chain of responsibility pattern. In fact, we have first tried to make a typical OO design, but then resorted to FP because of its simplicity. With this design, each handler accepts a continuation representing the rest of the handlers in the chain; the handler can either call this continuation or not. Using this simple model, we can express pretty much any handler pattern like `filter` and `filter_map`, using only functions and nothing else. You do not need complex programming machinery such as abstract factories, builders, etc.\n\n### DI\n\nIn Rust, it is possible to express type-safe DI that checks all types statically. However, this would require complex type-level manipulations, such as those in the [`frunk`] library. We decided not to trade comprehensible error messages for compile-time safety, since we had a plenty of experience that the uninitiated users simply cannot understand what is wrong with their code, owing to the utterly inadequate diagnostic messages from rustc.\n\nThe approach taken by `dptree` is to implement run-time type checking instead, via `dptree::type_check`, to make sure that all required types are provided _before_ execution. If `dptree::type_check` is _not_ called, type checking will be delayed until execution (this is not recommended). This approach works much like static type checking when you build your whole dispatch tree at a program startup; the panic message raised by `dptree::type_check` even [shows the exact locations] in user code that require insatisfied dependencies!\n\n[shows the exact locations]: https://github.com/teloxide/dptree/blob/master/examples/diagnostics.rs\n[`frunk`]: https://github.com/lloydmeta/frunk\n\n## Troubleshooting\n\n### `the trait bound [closure@examples/state_machine.rs:150:20: 150:92]: Injectable\u003c_, bool, _\u003e is not satisfied`\n\nThis error means that your handler does not implement the `Injectable` trait. Ensure that your update type implements `Clone`. If it is too expensive to clone every single update, you can wrap it into `Arc`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteloxide%2Fdptree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fteloxide%2Fdptree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fteloxide%2Fdptree/lists"}