1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
//! Structures for handling gRPC request metadata.
//!
//! This module has structures and functions for extracting and inserting
//! metadata on gRPC requests.
use tonic::metadata::{MetadataMap, MetadataValue};
use tonic::service::Interceptor;
use tonic::{Request, Status};
/// Retrieve the string value from a request's metadata map by its key.
///
/// # Example
///
/// ```ignore
/// let tenant = get_value(request.metadata(), "tenant")
/// .expect("Missing tenant info on request metadata");
/// ```
pub fn get_value(map: &MetadataMap, key: &str) -> Option<String> {
map.get(key).map(|v| v.to_str().unwrap_or("unknown").into())
}
/// Emplace tenant and requestor information on a request's metadata.
///
/// Returns a string indicating the kind of error if it fails.
pub fn push_metadata(
request: &mut Request<()>,
tenant: &str,
requestor: &str,
) -> Result<(), String> {
request.metadata_mut().insert(
"tenant",
MetadataValue::try_from(tenant)
.map_err(|e| format!("Failure while applying tenant to request: {}", e))?,
);
request.metadata_mut().insert(
"requestor",
MetadataValue::try_from(requestor)
.map_err(|e| format!("Failure while applying requestor to request: {}", e))?,
);
Ok(())
}
/// Defines the interceptor data for any gRPC client.
///
/// The interceptor exists for the sole purpose of introducing `tenant` and
/// `requestor` data within a given request, that is going to be sent by the
/// client connected to a service.
///
/// # Example
///
/// Consider the following dummy service that allows a remote procedure call
/// to a `ping` function.
///
/// ```proto
/// // example.proto
///
/// syntax = "proto3";
/// import "google/protobuf/empty.proto";
///
/// package Example;
///
/// service Example {
/// rpc ping(google.protobuf.Empty) returns (google.protobuf.Empty) {}
/// }
/// ```
///
/// Imagine that this single procedure implementation requires that tenant and
/// requestor data are embedded within a request for this to work. For example:
///
/// ```ignore
/// // example.rs
/// tonic::include_proto!("example");
///
/// #[derive(Clone)]
/// pub struct ExampleService;
///
/// #[tonic::async_trait]
/// impl Example for ExampleService {
/// async fn ping(&self, req: Request<()>) -> Result<(), Status> {
/// let tenant = metadata::get_value(req.metadata(), "tenant")
/// .ok_or_else(|| Status::failed_precondition("Missing tenant data"))?;
/// let requestor = metadata::get_value(req.metadata(), "requestor")
/// .ok_or_else(|| Status::failed_precondition("Missing requestor data"))?;
/// println!("Tenant: {}, requestor: {}", tenant, requestor);
/// Ok(Response::new(()))
/// }
/// }
/// ```
///
/// One may want to use this structure to send data as needed.
///
/// ```ignore
/// // example.rs
/// fn test_request() {
/// use tonic::transport::Channel;
/// let channel = Channel::from_static("http://localhost:1234").connect().await?;
/// let client = test_client::TestClient::with_interceptor(
/// channel,
/// ClientInterceptor::new("company", "admin"),
/// );
/// let response = client.ping(Request::new(())).await?;
/// }
/// ```
pub struct ClientInterceptor {
/// Tenant related to the database access while on a request.
tenant: String,
/// Requestor of the operation, usually the login of a user.
requestor: String,
}
impl ClientInterceptor {
/// Creates a new `ClientInterceptor` from given tenant and requestor
/// names. The tenant is closely related to the database to be manipulated,
/// while the requestor usually is the login of the user performing the
/// manipulation.
pub fn new(tenant: &str, requestor: &str) -> Self {
Self {
tenant: tenant.trim().into(),
requestor: requestor.trim().into(),
}
}
}
impl Interceptor for ClientInterceptor {
fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
push_metadata(&mut request, self.tenant.as_ref(), self.requestor.as_ref())
.map_err(Status::failed_precondition)?;
Ok(request)
}
}