feat(build): Better support for custom codecs (#999)
BREAKING CHANGE: `CODEC_PATH` moved from const to fn
This commit is contained in:
@@ -186,6 +186,14 @@ path = "src/streaming/client.rs"
|
||||
name = "streaming-server"
|
||||
path = "src/streaming/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "json-codec-client"
|
||||
path = "src/json-codec/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "json-codec-server"
|
||||
path = "src/json-codec/server.rs"
|
||||
|
||||
[dependencies]
|
||||
async-stream = "0.3"
|
||||
futures = { version = "0.3", default-features = false, features = ["alloc"] }
|
||||
|
||||
+27
-2
@@ -1,5 +1,4 @@
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
tonic_build::configure()
|
||||
@@ -30,4 +29,30 @@ fn main() {
|
||||
&["proto/googleapis"],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
build_json_codec_service();
|
||||
}
|
||||
|
||||
// Manually define the json.helloworld.Greeter service which used a custom JsonCodec to use json
|
||||
// serialization instead of protobuf for sending messages on the wire.
|
||||
// This will result in generated client and server code which relies on its request, response and
|
||||
// codec types being defined in a module `crate::common`.
|
||||
//
|
||||
// See the client/server examples defined in `src/json-codec` for more information.
|
||||
fn build_json_codec_service() {
|
||||
let greeter_service = tonic_build::manual::Service::builder()
|
||||
.name("Greeter")
|
||||
.package("json.helloworld")
|
||||
.method(
|
||||
tonic_build::manual::Method::builder()
|
||||
.name("say_hello")
|
||||
.route_name("SayHello")
|
||||
.input_type("crate::common::HelloRequest")
|
||||
.output_type("crate::common::HelloResponse")
|
||||
.codec_path("crate::common::JsonCodec")
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
||||
tonic_build::manual::Builder::new().compile(&[greeter_service]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//! A HelloWorld example that uses JSON instead of protobuf as the message serialization format.
|
||||
//!
|
||||
//! Generated code is the output of codegen as defined in the `build_json_codec_service` function
|
||||
//! in the `examples/build.rs` file. As defined there, the generated code assumes that a module
|
||||
//! `crate::common` exists which defines `HelloRequest`, `HelloResponse`, and `JsonCodec`.
|
||||
|
||||
pub mod common;
|
||||
use common::HelloRequest;
|
||||
|
||||
pub mod hello_world {
|
||||
include!(concat!(env!("OUT_DIR"), "/json.helloworld.Greeter.rs"));
|
||||
}
|
||||
use hello_world::greeter_client::GreeterClient;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
|
||||
|
||||
let request = tonic::Request::new(HelloRequest {
|
||||
name: "Tonic".into(),
|
||||
});
|
||||
|
||||
let response = client.say_hello(request).await?;
|
||||
|
||||
println!("RESPONSE={:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
//! This module defines common request/response types as well as the JsonCodec that is used by the
|
||||
//! json.helloworld.Greeter service which is defined manually (instead of via proto files) by the
|
||||
//! `build_json_codec_service` function in the `examples/build.rs` file.
|
||||
|
||||
use bytes::{Buf, BufMut};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
use tonic::{
|
||||
codec::{Codec, DecodeBuf, Decoder, EncodeBuf, Encoder},
|
||||
Status,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct HelloRequest {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct HelloResponse {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JsonEncoder<T>(PhantomData<T>);
|
||||
|
||||
impl<T: serde::Serialize> Encoder for JsonEncoder<T> {
|
||||
type Item = T;
|
||||
type Error = Status;
|
||||
|
||||
fn encode(&mut self, item: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Self::Error> {
|
||||
serde_json::to_writer(buf.writer(), &item).map_err(|e| Status::internal(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JsonDecoder<U>(PhantomData<U>);
|
||||
|
||||
impl<U: serde::de::DeserializeOwned> Decoder for JsonDecoder<U> {
|
||||
type Item = U;
|
||||
type Error = Status;
|
||||
|
||||
fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result<Option<Self::Item>, Self::Error> {
|
||||
if !buf.has_remaining() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let item: Self::Item =
|
||||
serde_json::from_reader(buf.reader()).map_err(|e| Status::internal(e.to_string()))?;
|
||||
Ok(Some(item))
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Codec`] that implements `application/grpc+json` via the serde library.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct JsonCodec<T, U>(PhantomData<(T, U)>);
|
||||
|
||||
impl<T, U> Default for JsonCodec<T, U> {
|
||||
fn default() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Codec for JsonCodec<T, U>
|
||||
where
|
||||
T: serde::Serialize + Send + 'static,
|
||||
U: serde::de::DeserializeOwned + Send + 'static,
|
||||
{
|
||||
type Encode = T;
|
||||
type Decode = U;
|
||||
type Encoder = JsonEncoder<T>;
|
||||
type Decoder = JsonDecoder<U>;
|
||||
|
||||
fn encoder(&mut self) -> Self::Encoder {
|
||||
JsonEncoder(PhantomData)
|
||||
}
|
||||
|
||||
fn decoder(&mut self) -> Self::Decoder {
|
||||
JsonDecoder(PhantomData)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//! A HelloWorld example that uses JSON instead of protobuf as the message serialization format.
|
||||
//!
|
||||
//! Generated code is the output of codegen as defined in the `build_json_codec_service` function
|
||||
//! in the `examples/build.rs` file. As defined there, the generated code assumes that a module
|
||||
//! `crate::common` exists which defines `HelloRequest`, `HelloResponse`, and `JsonCodec`.
|
||||
|
||||
use tonic::{transport::Server, Request, Response, Status};
|
||||
|
||||
pub mod common;
|
||||
use common::{HelloRequest, HelloResponse};
|
||||
|
||||
pub mod hello_world {
|
||||
include!(concat!(env!("OUT_DIR"), "/json.helloworld.Greeter.rs"));
|
||||
}
|
||||
use hello_world::greeter_server::{Greeter, GreeterServer};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MyGreeter {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Greeter for MyGreeter {
|
||||
async fn say_hello(
|
||||
&self,
|
||||
request: Request<HelloRequest>,
|
||||
) -> Result<Response<HelloResponse>, Status> {
|
||||
println!("Got a request from {:?}", request.remote_addr());
|
||||
|
||||
let reply = HelloResponse {
|
||||
message: format!("Hello {}!", request.into_inner().name),
|
||||
};
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "[::1]:50051".parse().unwrap();
|
||||
let greeter = MyGreeter::default();
|
||||
|
||||
println!("GreeterServer listening on {}", addr);
|
||||
|
||||
Server::builder()
|
||||
.add_service(GreeterServer::new(greeter))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user