gRPC Fundamentals & Contracts
Sword uses tonic as the foundation for gRPC controllers. In Sword, .proto files define the public contract of your gRPC service: they are not read at runtime, they are compiled at build time, and they generate the Rust code for the service, the message types and, optionally, a descriptor for reflection.
Protocol Buffers
Protocol Buffers is an extensible, language- and platform-neutral mechanism, developed by Google, for serializing structured data.
You define how you want your data structured once, and afterwards you can use automatically generated source code, produced by a compilation process, to easily write and read your structured data across different data streams, using different programming languages.
RPC types
A service defined in .proto can expose several RPC types, depending on the shape of requests and responses:
- Unary: one request → one response. It is the most common type.
- Server streaming: the client sends one request and the server responds with a stream of messages.
- Client streaming: the client sends a stream of requests and the server responds with a single message.
- Bidirectional streaming: both sides send and receive streams concurrently.
In the contract they are declared like this:
syntax = "proto3";
package users;
service UserGrpcService {
rpc GetUser (GetUserRequest) returns (GetUserResponse); // unary
rpc StreamUsers (StreamUsersRequest) returns (stream UserItem); // server streaming
rpc UploadUsers (stream UserItem) returns (UploadReply); // client streaming
rpc Chat (stream ChatMessage) returns (stream ChatReply); // bidi streaming
}Recommendations and Naming Conventions
These rules aim to avoid ambiguity with your application's internal services and keep clear contracts for external clients.
Recommended location
We recommend keeping the .proto files in config/proto/, or in shared/proto/ if it is a contract shared across several services.
This helps you:
- maintain a predictable structure across projects,
- reuse the same pattern in
build.rs, - separate transport contracts from the rest of the application code.
Services
Use the GrpcService suffix in proto services, because business services with similar names can coexist in your application code.
service UserGrpcService {
rpc GetUser (GetUserRequest) returns (GetUserResponse);
}service UserService {
rpc GetUser (GetUserRequest) returns (GetUserResponse);
}RPC methods
Name methods by intent (Get, List, Create, Update, Delete, Stream) so the contract is self-explanatory for whoever consumes it.
rpc GetUser (GetUserRequest) returns (GetUserResponse);
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
rpc StreamUsers (StreamUsersRequest) returns (stream User);rpc UserGet (GetUserRequest) returns (GetUserResponse);
rpc DoUserAction (ListUsersRequest) returns (ListUsersResponse);Messages
Avoid technical prefixes/suffixes such as Grpc in message. In public contracts, it is better to use neutral, domain-oriented names.
message GetUserRequest {
string id = 1;
}
message GetUserResponse {
User user = 1;
}message GetUserGrpcRequest {
string id = 1;
}
message GetUserGrpcResponse {
UserGrpcModel user = 1;
}Build dependencies
To compile the .proto files you need tonic-prost-build as a build dependency, plus prost (serialization) and tonic-prost (codec) as project dependencies.
Minimum dependencies
[dependencies]
sword = { version = "x.y.z", features = ["grpc"] }
tonic = "x.y.z"
prost = "x.y.z"
prost-types = "x.y.z"
tonic-prost = "x.y.z"
[build-dependencies]
tonic-prost-build = "x.y.z"Why is sword with its grpc feature not enough?
When .proto files are compiled, they depend directly on prost and tonic-prost in the final client, meaning these crates need to be in the project's Cargo.toml; it is not enough for sword to re-export them.
Advanced types with prost-types
If your contract uses protobuf well-known types (google.protobuf.Timestamp, Duration, Any, etc.), you also need the prost-types crate in the project.
import "google/protobuf/timestamp.proto";
message UserItem {
string id = 1;
string username = 2;
google.protobuf.Timestamp created_at = 3;
}pub struct User {
pub id: String,
pub username: String,
pub created_at: prost_types::Timestamp,
}Compiling .proto files in build.rs
Any project using gRPC controllers needs a build.rs file that compiles the .proto files with tonic_prost_build.
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_prost_build::configure()
.compile_protos(&["config/proto/users.proto"], &["config/proto"])?;
Ok(())
}What does compiling a .proto generate?
When you compile a .proto file with tonic-prost-build, it generates:
- A trait for the defined service (for example
UserGrpcService), - A server to register the service (for example
UserGrpcServiceServer<T>), - A client to invoke the service (for example
UserGrpcServiceClient<T>), - Request/response types and enums defined in the contract.
Reflection
If you enable the grpc-reflection feature, you must generate the reflection descriptor. This lets Sword register reflection automatically and expose it to clients such as grpcurl.
[dependencies]
sword = { version = "x.y.z", features = ["grpc", "grpc-reflection"] }use std::{env, path::PathBuf};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let descriptor_path = out_dir.join("sword_descriptor_set.bin");
tonic_prost_build::configure()
.file_descriptor_set_path(&descriptor_path)
.compile_protos(&["config/proto/users.proto"], &["config/proto"])?;
Ok(())
}
