crates/gunnar-grpc/src/status.rs
//! The one place a [`ControlError`] becomes a `tonic::Status`, and the one
//! place a request's `RepoPath` is validated.
//!
//! Seven services would otherwise each grow their own `match` over the same
//! error enum and their own "is the path present and sane" check, well past
//! the ten-duplicated-lines line one implementation draws. Fixing it by construction means
//! there is exactly one writer of each mapping, so the two cannot disagree.
//!
//! `clippy::result_large_err` is allowed here and only here: `tonic::Status`
//! is a large error type by design (it carries metadata and a source), and
//! every RPC in the surface returns one. Boxing it would fight the framework
//! for no gain.
#![allow(clippy::result_large_err)]
use gunnar_proto::RepoPath;
use tonic::Status;
use crate::seam::ControlError;
impl From<ControlError> for Status {
fn from(error: ControlError) -> Self {
let message = error.to_string();
match error {
ControlError::NotFound(_) => Status::not_found(message),
ControlError::AlreadyExists(_) => Status::already_exists(message),
ControlError::InvalidArgument(_) => Status::invalid_argument(message),
ControlError::FailedPrecondition(_) => Status::failed_precondition(message),
ControlError::ResourceExhausted(_) => Status::resource_exhausted(message),
ControlError::Unimplemented(_) => Status::unimplemented(message),
ControlError::Internal(_) => Status::internal(message),
ControlError::Unavailable(_) => Status::unavailable(message),
ControlError::Unauthenticated(_) => Status::unauthenticated(message),
ControlError::PermissionDenied(_) => Status::permission_denied(message),
}
}
}
/// A required message field that was not sent.
pub(crate) fn require<T>(field: Option<T>, name: &str) -> Result<T, Status> {
field.ok_or_else(|| Status::invalid_argument(format!("`{name}` is required")))
}
/// Pull the `<namespace>/<repo>` out of a request and check its shape (multi-tenancy).
///
/// Every repo-scoped RPC goes through this. A path is never defaulted: an
/// absent namespace is an error, not "the default namespace", because a
/// control plane that invents a tenant is a control plane that writes into the
/// wrong one.
pub(crate) fn repo_path(field: Option<RepoPath>, name: &str) -> Result<RepoPath, Status> {
let path = require(field, name)?;
path.validate()
.map_err(|e| Status::invalid_argument(format!("`{name}`: {e}")))?;
Ok(path)
}
/// A repo path that may legitimately be absent. Validated when it is present.
pub(crate) fn opt_repo_path(
field: Option<RepoPath>,
name: &str,
) -> Result<Option<RepoPath>, Status> {
match field {
None => Ok(None),
Some(path) => {
path.validate()
.map_err(|e| Status::invalid_argument(format!("`{name}`: {e}")))?;
Ok(Some(path))
}
}
}
/// `""` means "no filter" throughout the surface; this is that convention in
/// one place rather than seven.
pub(crate) fn opt_str(value: &str) -> Option<&str> {
if value.is_empty() {
None
} else {
Some(value)
}
}