use std::net::SocketAddr;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum GrpcError {
#[error("Transport error: {0}")]
Transport(String),
#[error("Auth error: {0}")]
Auth(String),
}
pub struct AuthInterceptor {
secret: Vec<u8>,
}
impl AuthInterceptor {
pub fn new(secret: Vec<u8>) -> Self {
Self { secret }
}
pub fn extract_token(metadata: &tonic::metadata::MetadataMap) -> Option<&str> {
metadata
.get("authorization")
.and_then(|v| v.to_str().ok())
.map(|s| s.strip_prefix("Bearer ").unwrap_or(s))
}
pub fn verify(&self, token: &str) -> Result<(), GrpcError> {
if token.is_empty() {
return Err(GrpcError::Auth("empty token".into()));
}
if self.secret.is_empty() {
return Err(GrpcError::Auth("secret not configured".into()));
}
Ok(())
}
}
#[cfg(feature = "grpc")]
pub async fn serve_grpc(
router: tonic::transport::server::Router,
addr: SocketAddr,
) -> Result<(), GrpcError> {
router
.serve(addr)
.await
.map_err(|e| GrpcError::Transport(e.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auth_interceptor_new() {
let interceptor = AuthInterceptor::new(b"secret".to_vec());
assert_eq!(interceptor.secret, b"secret");
}
#[test]
fn test_auth_interceptor_verify() {
let interceptor = AuthInterceptor::new(b"secret".to_vec());
assert!(interceptor.verify("valid_token").is_ok());
assert!(interceptor.verify("").is_err());
}
#[test]
fn test_auth_interceptor_no_secret() {
let interceptor = AuthInterceptor::new(vec![]);
assert!(interceptor.verify("token").is_err());
}
#[test]
fn test_grpc_error_display() {
let err = GrpcError::Transport("connection refused".into());
assert_eq!(err.to_string(), "Transport error: connection refused");
let err = GrpcError::Auth("invalid token".into());
assert_eq!(err.to_string(), "Auth error: invalid token");
}
#[test]
fn test_extract_token() {
let mut metadata = tonic::metadata::MetadataMap::new();
metadata.insert("authorization", "Bearer abc123".parse().unwrap());
let token = AuthInterceptor::extract_token(&metadata);
assert_eq!(token, Some("abc123"));
}
#[test]
fn test_extract_token_missing() {
let metadata = tonic::metadata::MetadataMap::new();
let token = AuthInterceptor::extract_token(&metadata);
assert_eq!(token, None);
}
}