GRPC Annotations in Tonic (Rust)


One pretty neat feature to have when operating a GRPC API is documentation. It can be almost cost-free by using annotations. With Tonic, it is possible to have the endpoints auto documented into a Swagger or OpenAPI documentation.

Lets try for this api specification,

grpc/echo.proto:

    syntax = "proto3";
    package api;
    option go_package = "api";

    import "google/api/annotations.proto";

    message Message {
        string value = 1;
    }
        
    service EchoService {
        rpc Echo(Message) returns (Message) {
                option (google.api.http) = {
                post: "/v1/example/echo"
                body: "*"
            };
        }
    }

and this rust implementation,

src/main.rs:

    use tonic::{transport::Server, Request, Response, Status};

    use crate::api::echo_service_server::{EchoService, EchoServiceServer};
    use api::Message;

    pub mod api {
        tonic::include_proto!("api");
    }

    #[derive(Debug, Default)]
    pub struct EchoAPI {}

    #[tonic::async_trait]
    impl EchoService for EchoAPI {
        async fn echo(
            &self,
            request: Request<Message>,
        ) -> Result<Response<Message>, Status> {
            let message = request.into_inner();

            println!("received {:?}", message.value);

            Ok(Response::new(Message { value: "response".into() }))
        }
    }


    #[tokio::main]
    async fn main() -> Result<(), Box<dyn std::error::Error>> {
        let addr = "0.0.0.0:7000".parse()?;

        println!("listening on port {:?}", addr);

        Server::builder()
            .add_service(EchoServiceServer::new(EchoAPI::default()))
            .serve(addr)
            .await?;

        Ok(())
    }

and build.rs:

    use failure::{Error};

    fn main() -> Result<(), Error> {
        tonic_build::configure()
        .compile(
            &["grpc/echo.proto"],
            &["googleapis", "grpc"],
        )?;

        Ok(())
    }

and in Cargo.toml:

[dependencies]
failure             = "^0.1" // ༼◕_◕༽ anyhow would be better
prost               = "0.6"
tonic               = { version = "^0.3", features = ["tls"] }
tokio               = { version = "^0.2", features = ["macros"] }


[build-dependencies]
failure             = "^0.1"
tonic-build         = "^0.3"

then add .gitmodules:

    [submodule "googleapis"]
    path = googleapis
    url = git@github.com:googleapis/googleapis.git

don’t forget to git submodule update --remote

You need golang, the protobuf compiler and swagger

To install protoc on linux:

    #! /bin/bash
    apt-get -qq update
    apt-get install -y  build-essential wget zip

    wget https://github.com/protocolbuffers/protobuf/releases/download/v3.13.0/protoc-3.13.0-linux-x86_64.zip
    unzip protoc-3.13.0-linux-x86_64.zip
    rm -f *.zip
    mv bin/* /usr/local/bin
    mv include/* /usr/local/include/
    protoc --version

To install/update gprc gateway extensions:

    go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway &&\
            go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger     &&\
            go get -u github.com/golang/protobuf/protoc-gen-go

To generate swagger documentation:

    protoc  -I/usr/local/include -I.            \
        -I$GOPATH/src                       \
        -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
        --swagger_out=logtostderr=true:.    \
        echo.proto
docker run -p 80:8080 -e BASE_URL=/ -e SWAGGER_JSON=/swag/echo.swagger.json -v `pwd`:/swag swaggerapi/swagger-ui

With this documentation and observability offered by opentelemetry-rust, you can have production ready grpc api in Rust.

From here, just enjoy the ride.