use prost::Message;
use std::fmt::Debug;
use tonic::{Code, Request, Status};
use tonic_mock::client_mock::{GrpcClientExt, MockResponseDefinition, MockableGrpcClient};
#[derive(Debug, Clone)]
pub struct UserServiceClient<T> {
inner: T,
}
#[derive(Clone, PartialEq, Message)]
pub struct GetUserRequest {
#[prost(string, tag = "1")]
pub user_id: String,
}
#[derive(Clone, PartialEq, Message)]
pub struct User {
#[prost(string, tag = "1")]
pub id: String,
#[prost(string, tag = "2")]
pub name: String,
#[prost(string, tag = "3")]
pub email: String,
}
impl GrpcClientExt<UserServiceClient<MockableGrpcClient>>
for UserServiceClient<MockableGrpcClient>
{
fn with_mock(mock: MockableGrpcClient) -> Self {
Self { inner: mock }
}
}
impl UserServiceClient<MockableGrpcClient> {
pub async fn get_user(
&mut self,
request: Request<GetUserRequest>,
) -> Result<tonic::Response<User>, Status> {
let client = &self.inner;
let request_data = request.into_inner();
println!("Debug: Sending request with user_id: {:?}", request_data);
let encoded = tonic_mock::grpc_mock::encode_grpc_request(request_data);
let (response_bytes, http_metadata) = client
.handle_request("user.UserService", "GetUser", &encoded)
.await?;
let user: User = tonic_mock::grpc_mock::decode_grpc_message(&response_bytes)?;
let mut tonic_response = tonic::Response::new(user);
if let Some(key) = http_metadata.get("x-request-id") {
if let Ok(val_str) = key.to_str() {
tonic_response
.metadata_mut()
.insert("x-request-id", val_str.parse().unwrap());
}
}
Ok(tonic_response)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Mockable gRPC Client Example ===\n");
println!("Example 1: Basic Usage");
basic_usage_example().await?;
println!("\nExample 2: Single Response");
single_response_example().await?;
println!("\nExample 3: Conditional Responses");
conditional_response_example().await?;
println!("\nExample 4: Error Handling");
error_handling_example().await?;
println!("\nExample 5: Simulating Delays");
delay_example().await?;
Ok(())
}
async fn basic_usage_example() -> Result<(), Box<dyn std::error::Error>> {
let mock = MockableGrpcClient::new();
mock.mock::<GetUserRequest, User>("user.UserService", "GetUser")
.respond_with(
MockResponseDefinition::ok(User {
id: "user123".to_string(),
name: "Alice Smith".to_string(),
email: "alice@example.com".to_string(),
})
.with_metadata("x-request-id", "req-123"),
)
.await;
let mut client = UserServiceClient::with_mock(mock);
let request = GetUserRequest {
user_id: "user123".to_string(),
};
let response = client.get_user(Request::new(request)).await?;
let user = response.get_ref();
println!("Retrieved user: {} <{}>", user.name, user.email);
println!("Response metadata: {:?}", response.metadata());
Ok(())
}
async fn single_response_example() -> Result<(), Box<dyn std::error::Error>> {
let mock = MockableGrpcClient::new();
mock.mock::<GetUserRequest, User>("user.UserService", "GetUser")
.respond_with(MockResponseDefinition::ok(User {
id: "default-user".to_string(),
name: "Default User".to_string(),
email: "default@example.com".to_string(),
}))
.await;
let mut client = UserServiceClient::with_mock(mock);
let request = GetUserRequest {
user_id: "any-id".to_string(),
};
let response = client.get_user(Request::new(request)).await?;
let user = response.get_ref();
println!("Retrieved user: {} <{}>", user.name, user.email);
Ok(())
}
async fn conditional_response_example() -> Result<(), Box<dyn std::error::Error>> {
let mock = MockableGrpcClient::new();
let alice = User {
id: "user123".to_string(),
name: "Alice Smith".to_string(),
email: "alice@example.com".to_string(),
};
let bob = User {
id: "user456".to_string(),
name: "Bob Jones".to_string(),
email: "bob@example.com".to_string(),
};
mock.mock::<GetUserRequest, User>("user.UserService", "GetUser")
.respond_when(
|req| req.user_id == "user123",
MockResponseDefinition::ok(alice),
)
.await
.respond_when(
|req| req.user_id == "user456",
MockResponseDefinition::ok(bob),
)
.await;
let mut client = UserServiceClient::with_mock(mock);
for user_id in &["user123", "user456"] {
let request = GetUserRequest {
user_id: user_id.to_string(),
};
let response = client.get_user(Request::new(request)).await?;
let user = response.get_ref();
println!("User {}: {} <{}>", user_id, user.name, user.email);
}
Ok(())
}
async fn error_handling_example() -> Result<(), Box<dyn std::error::Error>> {
let mock = MockableGrpcClient::new();
mock.mock::<GetUserRequest, User>("user.UserService", "GetUser")
.respond_when(
|req| req.user_id == "valid-user",
MockResponseDefinition::ok(User {
id: "valid-user".to_string(),
name: "Valid User".to_string(),
email: "valid@example.com".to_string(),
}),
)
.await
.respond_when(
|req| req.user_id == "not-found",
MockResponseDefinition::err(Status::new(Code::NotFound, "User not found")),
)
.await
.respond_when(
|req| req.user_id == "unauthorized",
MockResponseDefinition::err(Status::new(
Code::PermissionDenied,
"Not authorized to access this user",
)),
)
.await;
let mut client = UserServiceClient::with_mock(mock);
for user_id in &["valid-user", "not-found", "unauthorized"] {
let request = GetUserRequest {
user_id: user_id.to_string(),
};
match client.get_user(Request::new(request)).await {
Ok(response) => {
let user = response.get_ref();
println!("Success: User {} found: {}", user.id, user.name);
}
Err(status) => {
println!(
"Error for {}: {} (code: {:?})",
user_id,
status.message(),
status.code()
);
}
}
}
Ok(())
}
async fn delay_example() -> Result<(), Box<dyn std::error::Error>> {
let mock = MockableGrpcClient::new();
mock.mock::<GetUserRequest, User>("user.UserService", "GetUser")
.respond_when(
|req| req.user_id == "fast-user",
MockResponseDefinition::ok(User {
id: "fast-user".to_string(),
name: "Fast Response".to_string(),
email: "fast@example.com".to_string(),
})
.with_delay(100), )
.await
.respond_when(
|req| req.user_id == "slow-user",
MockResponseDefinition::ok(User {
id: "slow-user".to_string(),
name: "Slow Response".to_string(),
email: "slow@example.com".to_string(),
})
.with_delay(500), )
.await;
let mut client = UserServiceClient::with_mock(mock);
println!("Requesting fast user...");
let start = std::time::Instant::now();
let request = GetUserRequest {
user_id: "fast-user".to_string(),
};
let response = client.get_user(Request::new(request)).await?;
println!(
"Fast user response received in {:?}: {}",
start.elapsed(),
response.get_ref().name
);
println!("Requesting slow user...");
let start = std::time::Instant::now();
let request = GetUserRequest {
user_id: "slow-user".to_string(),
};
let response = client.get_user(Request::new(request)).await?;
println!(
"Slow user response received in {:?}: {}",
start.elapsed(),
response.get_ref().name
);
Ok(())
}