{"id":21659626,"url":"https://github.com/sourcefrog/prost-twirp","last_synced_at":"2025-07-17T22:32:34.901Z","repository":{"id":65419934,"uuid":"562692641","full_name":"sourcefrog/prost-twirp","owner":"sourcefrog","description":"Code generator and library for calling/serving Twirp services in Rust using prost and hyper","archived":false,"fork":true,"pushed_at":"2023-04-01T20:17:51.000Z","size":104,"stargazers_count":18,"open_issues_count":1,"forks_count":4,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-11-17T11:17:13.853Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"cretz/prost-twirp","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sourcefrog.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-11-07T03:35:32.000Z","updated_at":"2024-08-05T08:03:12.000Z","dependencies_parsed_at":"2023-02-01T17:45:58.902Z","dependency_job_id":null,"html_url":"https://github.com/sourcefrog/prost-twirp","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourcefrog%2Fprost-twirp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourcefrog%2Fprost-twirp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourcefrog%2Fprost-twirp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sourcefrog%2Fprost-twirp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sourcefrog","download_url":"https://codeload.github.com/sourcefrog/prost-twirp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226305717,"owners_count":17603862,"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":[],"created_at":"2024-11-25T09:31:18.987Z","updated_at":"2024-11-25T09:33:03.658Z","avatar_url":"https://github.com/sourcefrog.png","language":"Rust","funding_links":[],"categories":["Rust"],"sub_categories":[],"readme":"# Prost Twirp\n\nProst Twirp is a code generator and set of utilities for calling and serving [Twirp](https://github.com/twitchtv/twirp)\nservices in Rust, using the [prost](https://github.com/danburkert/prost/) and [hyper](https://github.com/hyperium/hyper)\nlibraries.\n\nTwirp is a simple cross-language framework/protocol for RPC, with services defined in Protobuf and transmitted by HTTP POST.\n\nSee usage detail below, [API docs](https://docs.rs/prost-twirp), and [examples](https://github.com/sourcefrog/prost-twirp/tree/master/examples).\n\n## Usage\n\nProst Twirp supports the calling and the serving of Twirp services. Prost Twirp can be used in one of three ways, each\nexplained in the following sections.\n\nBecause of the dynamically generated code and the interactions with complex Hyper types, the \nbest way to understand the API is to read and experiment with the `examples/`, in\nparticular `examples/service-gen`, which is the simplest.\n\n* As a client and/or server code generator along with a supporting runtime library. This is the simplest approach and strongly recommended.\n* As a library of utilities to help with more manual Twirp client/server invocations.\n\n### Generating Code\n\nSee `examples/service-gen` for a full working example.\n\nMost of the code generation relies on [prost](https://github.com/danburkert/prost/). The `prost` code generator accepts\na [prost_build::ServiceGenerator]. Prost Twirp provides this\ngenerator. \n\nThis walkthrough will use Twirp's [example service.proto](https://twitchtv.github.io/twirp/docs/example.html) that is also used by the\nProst Twirp's examples.\n\nSetup the project to generate code like the [prost-build](https://docs.rs/prost-build/) docs suggest. In addition, add\nthe following to the dependencies and build dependencies of `Cargo.toml`:\n\n```toml\n[dependencies]\nbytes = \"1.2\"\nfutures = \"0.3\"\nprost = \"0.11\"\nprost-derive = \"0.11\"\nprost-twirp = \"0.2\"\n\n[dependencies.hyper]\nversion = \"0.14\"\nfeatures = [\"client\", \"server\", \"http1\", \"http2\", \"tcp\"]\n\n[dependencies.tokio]\nversion = \"1.2\"\nfeatures = [\"macros\", \"net\", \"rt\", \"rt-multi-thread\", \"sync\", \"time\"]\n\n[build-dependencies]\nprost-build = \"0.11\"\nprost-twirp = { features = [\"service-gen\"] }\n```\n\nThis adds the supporting Prost Twirp library at runtime and the service generation support at build time. It also adds\n[hyper](https://hyper.rs/), [futures](https://docs.rs/futures), and [tokio](https://tokio.rs) that are needed to use the\nservice at runtime. Previously, the build script code in `build.rs` might have been:\n\n```rust,ignore\nfn main() {\n    prost_build::compile_protos(\u0026[\"src/service.proto\"], \u0026[\"src/\"]).unwrap();\n}\n```\n\nThat would just generate the protobuf structs, but not the service. Now change it to utilize the Prost Twirp service\ngenerator:\n\n```rust,ignore\nfn main() {\n    let mut conf = prost_build::Config::new();\n    conf.service_generator(Box::new(prost_twirp::TwirpServiceGenerator::new()));\n    conf.compile_protos(\u0026[\"src/service.proto\"], \u0026[\"src/\"]).unwrap();\n}\n```\n\nNow the included file contains a service client and server. As in the `prost-build` docs, it can be included in\n`main.rs`:\n\n```rust,ignore\nmod service {\n    include!(concat!(env!(\"OUT_DIR\"), \"/twitch.twirp.example.rs\"));\n}\n```\n\n### Generated Trait\n\nEach protobuf service maps to a single auto-generated Rust trait, with one method \ncorresponding to each method in the proto service interface. \n\nThe trait is implemented by an auto-generated client stub type, which translates \nmethod calls into HTTP requests to a remote Twirp server. \n\nIf you write a server, then your server will also provide an implementation \nof the same service trait, which when the methods are called will execute the business\nlogic of the method: for example, making a hat.\n\nThe [example service.proto](examples/service.proto) contains the\nfollowing service:\n\n```proto\n// A Haberdasher makes hats for clients.\nservice Haberdasher {\n  // MakeHat produces a hat of mysterious, randomly-selected color!\n  rpc MakeHat(Size) returns (Hat);\n}\n```\n\nThis generates the following trait in `target/..../twitch.twirp.example.rs`:\n\n```rust,ignore\npub trait Haberdasher: Send + Sync + 'static {\n    /// MakeHat produces a hat of mysterious, randomly-selected color!\n    fn make_hat(\n        \u0026self,\n        request: ::prost_twirp::ServiceRequest\u003cSize\u003e,\n    ) -\u003e ::prost_twirp::PTRes\u003cHat\u003e;\n}\n\nimpl dyn Haberdasher {\n    pub fn new_client(\n        client: ::hyper::Client\u003c::hyper::client::HttpConnector, ::hyper::Body\u003e,\n        root_url: \u0026str,\n    ) -\u003e Box\u003cdyn Haberdasher\u003e {\n        /* ... */\n    }\n\n    pub fn new_server\u003cT: Haberdasher\u003e(\n        v: T,\n    ) -\u003e Box\u003c\n        dyn (::hyper::service::Service\u003c\n            ::hyper::Request\u003c::hyper::Body\u003e,\n            Response = ::hyper::Response\u003c::hyper::Body\u003e,\n            Error = ::hyper::Error,\n            Future = ::std::pin::Pin\u003c\n                Box\u003c\n                    dyn (::futures::Future\u003c\n                        Output = Result\u003c::hyper::Response\u003c::hyper::Body\u003e, ::hyper::Error\u003e,\n                    \u003e) + Send,\n                \u003e,\n            \u003e,\n        \u003e) + Send + Sync,\n    \u003e {\n        /* ... */\n    }\n}\n\n```\n\n[PTRes] is a boxed future service response, used by both the client and the server.\n\n### Using the Client\n\nCreating a Prost Twirp client is just an extra step after creating the\n[hyper::Client]. Simply call the `new_client` static method of the generated\nservice trait, passing the hyper client and a root URL like so:\n\n```rust,ignore\nlet hyper_client = Client::new();\nlet service_client =\n    \u003cdyn service::Haberdasher\u003e::new_client(hyper_client, \"http://localhost:8080\");\n```\n\nThis creates and returns a boxed implementation of the `Haberdasher` trait. Then it can be called like so:\n\n```rust,ignore\nlet res = service_client\n    .make_hat(service::Size { inches: 12 }.into())\n    .await\n    .unwrap();\nprintln!(\"Made {:?}\", res.output);\n```\n\nNotice the `into`, that turns a `prost` protobuf object into a Prost Twirp\n[ServiceRequest]. The result is a boxed\nfuture of the [ServiceResponse] whose `output`\nfield will contain the serialized result (in this case, `service::Hat`).\n\nAny error that can happen during the call results in an errored future with the\n[ProstTwirpError] error.\n\n### Using the Server\n\nThe same trait that is used for the client is what must be implemented as a server. Here is an example implementation:\n\n```rust,ignore\npub struct HaberdasherService;\nimpl service::Haberdasher for HaberdasherService {\n    fn make_hat(\n        \u0026self,\n        req: service::ServiceRequest\u003cservice::Size\u003e,\n    ) -\u003e service::PTRes\u003cservice::Hat\u003e {\n        Box::pin(future::ok(\n            service::Hat {\n                size: req.input.inches,\n                color: \"blue\".to_string(),\n                name: \"fedora\".to_string(),\n            }\n            .into(),\n        ))\n    }\n}\n```\n\nLike other Hyper services, this returns a boxed future with the protobuf value. In this case, it just generates an\ninstance of `Hat` every time. \n\nTo start the service, the generated trait has a\n`new_server` method that accepts an implementation of the trait and returns a `::hyper::service::Service`.\n\n```rust,ignore\nlet addr = \"0.0.0.0:8080\".parse().unwrap();\nlet server = Server::bind(\u0026addr)\n    .serve(make_service_fn(|_conn| async {\n        Ok::\u003c_, Infallible\u003e(\u003cdyn service::Haberdasher\u003e::new_server(HaberdasherService))\n    }));\nserver.await.unwrap();\n```\n\nNote, due to [some tokio service restrictions](https://github.com/tokio-rs/tokio-service/issues/9), the service\nimplementation has to have a `'static` lifetime.\n\n## Returning Errors\n\nErrors can be returned which are in the form of a [ProstTwirpError]. A [TwirpError], which corresponds more directly\nto the Twirp serialized error format can be sent back instead. \n\n(See `examples/errors` for a full working example.)\n\nHere is an example of not accepting any size outside of some bounds:\n\n```rust,ignore\npub struct HaberdasherService;\nimpl service::Haberdasher for HaberdasherService {\n    fn make_hat(\u0026self, i: service::ServiceRequest\u003cservice::Size\u003e) -\u003e service::PTRes\u003cservice::Hat\u003e {\n        Box::pin(if i.input.inches \u003c 1 {\n            future::err(\n                TwirpError::new_meta(\n                    StatusCode::BAD_REQUEST,\n                    \"too_small\",\n                    \"Size too small\",\n                    serde_json::to_value(MinMaxSize { min: 1, max: 10 }).ok(),\n                )\n                .into(),\n            )\n        } else if i.input.inches \u003e 10 {\n            future::err(\n                TwirpError::new_meta(\n                    StatusCode::BAD_REQUEST,\n                    \"too_large\",\n                    \"Size too large\",\n                    serde_json::to_value(MinMaxSize { min: 1, max: 10 }).ok(),\n                )\n                .into(),\n            )\n        } else {\n            future::ok(\n                service::Hat {\n                    size: i.input.inches,\n                    color: \"blue\".to_string(),\n                    name: \"fedora\".to_string(),\n                }\n                .into(),\n            )\n        })\n    }\n}\n```\n\nMetadata in the form of a [serde_json::Value] can be given to a [TwirpError] as well. \n\n### Manual Client and Server\n\nInstead of code generation, some of the features of Prost Twirp can be used manually.\nSee `examples/no-service-gen`. In this mode the application code is responsible for URL\nrouting and determining the right request and response type, and `prost_twirp` will\nde/serialize requests and responses.\n \nFor the client, a new [HyperClient] can be created\nwith the root URL and `hyper` client. Then, `go` can be invoked with a path and\na [ServiceRequest] for a `prost`-built message.\nThe response is a boxed future of a\n[ServiceResponse] that must be typed with the\nexpected `prost`-built output type. Example:\n\n### FAQ\n\n#### Why no JSON support?\n\nThis could be done soon, probably using [`pbjson`](https://docs.rs/pbjson/).\n\n#### Why does my server service impl have to be `'static`?\n\nThis is due to the need to reference the service inside of static futures. See\n[this issue](https://github.com/tokio-rs/tokio-service/issues/9). Any better solution is welcome.\n\n#### What Twirp format is supported?\n\nThis crate currently implements Twirp 5. Twirp 7 could be added.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsourcefrog%2Fprost-twirp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsourcefrog%2Fprost-twirp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsourcefrog%2Fprost-twirp/lists"}