twurst-server 0.1.0

Twirp server related code
Documentation
Crate implementing the needed runtime for the code generated by `twurst-build`
in order to run [Twirp](https://twitchtv.github.io/twirp/docs/spec_v7.html) servers.

## Getting started

To start you need first to have a gRPC `.proto` file (e.g. `service.proto`).

Then build your proto files by creating a `build.rs` file with:
```rust,ignore
fn main() -> std::io::Result<()> {
    twurst_build::TwirpBuilder::new()
        .with_server()
        .compile_protos(&["proto/service.proto"], &["proto"])
}
```

and add to your `Cargo.toml`:
```toml
[dependencies]
axum = ""
prost = ""
prost-types = ""
prost-reflect = ""
twurst-server = ""

[build-dependencies]
twurst-build = ""
```

Note that `protoc` must be available, see [`prost-build` documentation on this topic](https://docs.rs/prost-build/latest/prost_build/#sourcing-protoc).
If you are using Nix, `nix-shell -p protobuf` is enough to provide `protoc`.

Then you can implement a Twirp server with:
```rust,ignore
use proto::*;
use twurst_server::twirp_fallback;

mod proto {
    include!(concat!(env!("OUT_DIR"), "/example.rs")); // example is the name of your proto package
}

/// The service implementation
struct ExampleServiceServicer {}

impl ExampleService for ExampleServiceServicer {
    async fn test(
        &self,
        request: TestRequest
    ) -> Result<TestResponse, TwirpError> {
        unimplemented!()
    }
}

async fn main() {
    axum::serve(
        tokio::net::TcpListener::bind("localhost:8080").await?,
        axum::Router::new()
            .nest("/twirp", ExampleServiceServicer {}.into_router().fallback(twirp_fallback))
    ).await
}
```

Note that you can make use of [`tower`](https://docs.rs/tower) or [`tower-http`](https://docs.rs/tower-http) layers to customize the server:
```rust,ignore
use twurst_server::twirp_fallback;

async fn main() {
    axum::serve(
        tokio::net::TcpListener::bind("localhost:8080").await?,
        axum::Router::new()
            .nest("/twirp", ExampleServiceServicer {}.into_router().fallback(twirp_fallback))
            .layer(tower_http::cors::CorsLayer::new())
    ).await
}
```

It is also possible to use [`axum` extractors](https://docs.rs/axum/latest/axum/extract/index.html) in the generated code.
For example, to get access to the request headers you can tweak your `build.rs` `TwirpBuilder::new()` call:
```rust,ignore
    twurst_build::TwirpBuilder::new()
        .with_server()
        .with_axum_request_extractor("headers", "::axum::http::HeaderMap")
        .compile_protos(&["proto/service.proto"], &["proto"])
```
then your trait implementation will change to:
```rust,ignore
impl self::proto::ExampleService for ExampleServiceServicer {
    async fn test(
        &self,
        request: TestRequest,
        headers: ::axum::http::HeaderMap
    ) -> Result<TestResponse, TwirpError> {
        unimplemented!()
    }
}
```
Any type implementing [`FromRequestParts`](https://docs.rs/axum/latest/axum/extract/trait.FromRequestParts.html) work.

Note that you can use [`Router::merge`](https://docs.rs/axum/latest/axum/struct.Router.html#method.merge) to serve multiple Twirp services:
```rust,ignore
use twurst_server::twirp_fallback;

ExampleServiceServicer {}
    .into_router()
    .merge(OtherExampleServiceServicer {}.into_router())
    .fallback(twirp_fallback)
```
Note the single `fallback` call.

To make testing easier you can use the generated client code to test your server:
```rust,ignore
use twurst_client::TwirpHttpClient;

let client = ExampleServiceClient::new(ExampleServiceServicer {}.into_router());
```

note that you need to add to your `build.rs` `.with_client()` alongside `.with_server()`.

## gRPC support

`twurst-server` has also basic gRPC support to serve easily both Twirp and gRPC.
The gRPC implementation supports both client and server streaming, opposite to Twirp.

For that enable the `grpc` feature of the `twurst-build` and `twurst-server` crates, then you can serve gRPC nearly like Twirp:
```rust,ignore
use twurst_server::grpc_fallback;

async fn main() {
    axum::serve(
        tokio::net::TcpListener::bind("localhost:8080").await?,
        ExampleServiceServicer {}.into_grpc_router().fallback(grpc_fallback)
    ).await
}
```
[`Router::merge`](https://docs.rs/axum/latest/axum/struct.Router.html#method.merge) still works if you want to serve multiple services.

Note that no limit is set on requests size, use [`RequestBodyLimit`](https://docs.rs/tower-http/latest/tower_http/limit/struct.RequestBodyLimit.html) layer if you want to set one.

## Cargo features
- `grpc` that provides gRPC support behind `tonic`

## License

Copyright 2024 Helsing GmbH

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.