├── example ├── src │ ├── proto.rs │ ├── auth.rs │ ├── products.rs │ ├── orders.rs │ ├── client.rs │ └── server.rs ├── README.md ├── build.rs ├── Cargo.toml └── proto │ └── estore.proto ├── integration_tests ├── src │ ├── lib.rs │ ├── proto.rs │ └── services.rs ├── build.rs ├── Cargo.toml ├── proto │ └── test_services.proto └── tests │ ├── common │ └── mod.rs │ └── tests.rs ├── .gitignore ├── src ├── lib.rs ├── middleware.rs └── request_interceptor.rs ├── Cargo.toml ├── .github └── workflows │ └── action.yaml ├── LICENSE ├── README.md └── Cargo.lock /example/src/proto.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/util.rs")); 2 | -------------------------------------------------------------------------------- /integration_tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod proto; 2 | pub mod services; 3 | -------------------------------------------------------------------------------- /integration_tests/src/proto.rs: -------------------------------------------------------------------------------- 1 | include!(concat!(env!("OUT_DIR"), "/util.rs")); 2 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example usage of `tonic-middleware` 2 | 3 | - `server.rs` runs the server 4 | - `clieint.rs` runs client 5 | 6 | See [documentation](https://github.com/teimuraz/tonic-middleware) for `tonic-middleware` usage 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | # Default ignored files 4 | /shelf/ 5 | /workspace.xml 6 | # Editor-based HTTP Client requests 7 | /httpRequests/ 8 | # Datasource local storage ignored files 9 | /dataSources/ 10 | /dataSources.local.xml 11 | .envrc -------------------------------------------------------------------------------- /example/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | fn main() -> Result<(), Box> { 5 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 6 | 7 | tonic_prost_build::configure() 8 | .build_server(true) 9 | .include_file("util.rs") 10 | .out_dir(out_dir.clone()) 11 | .file_descriptor_set_path(out_dir.clone().join("estore.bin")) 12 | .compile_protos(&["proto/estore.proto"], &["proto"])?; 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "server" 8 | path = "src/server.rs" 9 | 10 | [[bin]] 11 | name = "client" 12 | path = "src/client.rs" 13 | 14 | [dependencies] 15 | tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros"] } 16 | tonic = "0.14.1" 17 | tonic-prost = "0.14.1" 18 | prost = "0.14.1" 19 | 20 | [dependencies.tonic-middleware] 21 | path = ".." 22 | 23 | [build-dependencies] 24 | tonic-prost-build = "0.14.1" 25 | -------------------------------------------------------------------------------- /integration_tests/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | fn main() -> Result<(), Box> { 5 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); 6 | 7 | tonic_prost_build::configure() 8 | .build_server(true) 9 | .include_file("util.rs") 10 | .out_dir(out_dir.clone()) 11 | .file_descriptor_set_path(out_dir.clone().join("test_services.bin")) 12 | .compile_protos(&["proto/test_services.proto"], &["proto"])?; 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /integration_tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_tests" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | path = "src/lib.rs" 8 | 9 | [dependencies] 10 | tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros"] } 11 | tonic = "0.14" 12 | tonic-prost = "0.14" 13 | prost = "0.14" 14 | 15 | [dependencies.tonic-middleware] 16 | path = ".." 17 | 18 | [build-dependencies] 19 | tonic-prost-build = "0.14" 20 | 21 | [dev-dependencies] 22 | tokio = { version = "1.4", features = ["full", "test-util"] } 23 | serial_test = "3.2.0" 24 | -------------------------------------------------------------------------------- /example/src/auth.rs: -------------------------------------------------------------------------------- 1 | use tonic::async_trait; 2 | 3 | #[async_trait] 4 | pub trait AuthService: Send + Sync + 'static { 5 | async fn verify_token(&self, token: &str) -> Result; 6 | } 7 | 8 | #[derive(Default, Clone)] 9 | pub struct AuthServiceImpl; 10 | 11 | #[async_trait] 12 | impl AuthService for AuthServiceImpl { 13 | async fn verify_token(&self, token: &str) -> Result { 14 | if token == "supersecret" { 15 | Ok("user-1".to_string()) 16 | } else { 17 | Err("Unauthenticated".to_string()) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /integration_tests/proto/test_services.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package test_services; 3 | 4 | service PublicService { 5 | rpc PublicMethod(PublicMethodRequest) returns (PublicMethodResponse); 6 | } 7 | 8 | message PublicMethodRequest { 9 | string message = 1; 10 | } 11 | message PublicMethodResponse { 12 | string message = 1; 13 | } 14 | 15 | 16 | service ProtectedService { 17 | rpc ProtectedMethod(ProtectedMethodRequest) returns (ProtectedMethodResponse); 18 | } 19 | 20 | message ProtectedMethodRequest { 21 | string message = 1; 22 | } 23 | message ProtectedMethodResponse { 24 | string message = 1; 25 | string user_id = 2; 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use middleware::Middleware; 2 | pub use middleware::MiddlewareFor; 3 | pub use middleware::MiddlewareLayer; 4 | pub use request_interceptor::InterceptorFor; 5 | pub use request_interceptor::RequestInterceptor; 6 | pub use request_interceptor::RequestInterceptorLayer; 7 | 8 | use tonic::body::Body; 9 | use tonic::codegen::http::{Request, Response}; 10 | use tonic::codegen::Service; 11 | 12 | mod middleware; 13 | mod request_interceptor; 14 | 15 | pub trait ServiceBound: 16 | Service, Response = Response> + Send + Clone + 'static 17 | { 18 | } 19 | 20 | impl ServiceBound for T where 21 | T: Service, Response = Response> + Send + Clone + 'static 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /example/proto/estore.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package estore; 3 | 4 | service ProductService { 5 | rpc ListProducts(ListProductsRequest) returns (ListProductsResponse); 6 | } 7 | 8 | message ListProductsRequest { 9 | } 10 | 11 | message ListProductsResponse { 12 | repeated Product products = 1; 13 | } 14 | 15 | message Product { 16 | string id = 1; 17 | string name = 2; 18 | } 19 | 20 | 21 | service OrderService { 22 | rpc GetMyOrders(GetMyOrdersRequests) returns (GetMyOrdersResponse); 23 | } 24 | 25 | message GetMyOrdersRequests { 26 | } 27 | 28 | message GetMyOrdersResponse { 29 | repeated Order orders = 1; 30 | } 31 | 32 | message Order { 33 | string id = 1; 34 | string label = 2; 35 | int32 amount = 3; 36 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | workspace = { members = ["example", "integration_tests"] } 2 | [package] 3 | name = "tonic-middleware" 4 | authors = ["Teimuraz Kantariya "] 5 | categories = ["web-programming", "network-programming", "asynchronous", "authentication"] 6 | description = "Async middleware and interceptor for Tonic services" 7 | documentation = "https://github.com/teimuraz/tonic-middleware" 8 | edition = "2021" 9 | homepage = "https://github.com/teimuraz/tonic-middleware" 10 | keywords = ["middleware", "interceptor", "tonic", "async", "grpc"] 11 | license = "MIT" 12 | readme = "README.md" 13 | repository = "https://github.com/teimuraz/tonic-middleware" 14 | version = "0.4.0" 15 | 16 | [dependencies] 17 | tonic = "0.14" 18 | async-trait = "0.1" 19 | futures-util = "0.3" 20 | tower = "0.5" 21 | -------------------------------------------------------------------------------- /.github/workflows/action.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Update local toolchain 14 | run: | 15 | rustup update 16 | rustup component add clippy 17 | rustup install nightly 18 | - name: Install Protoc 19 | uses: arduino/setup-protoc@v3 20 | 21 | - name: Toolchain info 22 | run: | 23 | cargo --version --verbose 24 | rustc --version 25 | cargo clippy --version 26 | 27 | - name: Lint 28 | run: | 29 | cargo fmt -- --check 30 | cargo clippy -- -D warnings 31 | 32 | - name: Test 33 | run: | 34 | cargo check 35 | cargo test --all 36 | -------------------------------------------------------------------------------- /example/src/products.rs: -------------------------------------------------------------------------------- 1 | use crate::proto::estore::product_service_server::ProductService; 2 | use crate::proto::estore::{ListProductsRequest, ListProductsResponse, Product}; 3 | use tonic::{Request, Response, Status}; 4 | 5 | #[derive(Default)] 6 | pub struct Products {} 7 | 8 | #[tonic::async_trait] 9 | impl ProductService for Products { 10 | async fn list_products( 11 | &self, 12 | _request: Request, 13 | ) -> Result, Status> { 14 | Ok(Response::new(ListProductsResponse { 15 | products: vec![ 16 | Product { 17 | id: "p001".to_string(), 18 | name: "phone".to_string(), 19 | }, 20 | Product { 21 | id: "p002".to_string(), 22 | name: "tablet".to_string(), 23 | }, 24 | ], 25 | })) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Teimuraz Kantariya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/src/orders.rs: -------------------------------------------------------------------------------- 1 | use crate::proto::estore::order_service_server::OrderService; 2 | use crate::proto::estore::{GetMyOrdersRequests, GetMyOrdersResponse, Order}; 3 | use tonic::{Request, Response, Status}; 4 | 5 | #[derive(Default)] 6 | pub struct Orders {} 7 | 8 | #[tonic::async_trait] 9 | impl OrderService for Orders { 10 | async fn get_my_orders( 11 | &self, 12 | request: Request, 13 | ) -> Result, Status> { 14 | // user_id that is set within request interceptor 15 | let user_id = request.metadata().get("user_id"); 16 | println!("User Id {}", user_id.unwrap().to_str().unwrap()); 17 | Ok(Response::new(GetMyOrdersResponse { 18 | orders: vec![ 19 | Order { 20 | id: "ord001".to_string(), 21 | label: "Christmas gifts".to_string(), 22 | amount: 350, 23 | }, 24 | Order { 25 | id: "ord002".to_string(), 26 | label: "Home office equipment".to_string(), 27 | amount: 1150, 28 | }, 29 | ], 30 | })) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/src/client.rs: -------------------------------------------------------------------------------- 1 | pub mod proto; 2 | 3 | use crate::proto::estore::order_service_client::OrderServiceClient; 4 | use crate::proto::estore::product_service_client::ProductServiceClient; 5 | use crate::proto::estore::{GetMyOrdersRequests, ListProductsRequest}; 6 | use tonic::metadata::MetadataValue; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<(), Box> { 10 | let mut product_client = ProductServiceClient::connect("http://[::1]:50051").await?; 11 | let products_request = tonic::Request::new(ListProductsRequest {}); 12 | let list_products_response = product_client.list_products(products_request).await?; 13 | println!("List products response:\n {:?}\n", list_products_response); 14 | 15 | let mut order_client = OrderServiceClient::connect("http://[::1]:50051").await?; 16 | 17 | let token: MetadataValue<_> = "supersecret".parse()?; 18 | let mut orders_request_authenticated = tonic::Request::new(GetMyOrdersRequests {}); 19 | orders_request_authenticated 20 | .metadata_mut() 21 | .insert("authorization", token); 22 | let orders_response_authenticated = order_client 23 | .get_my_orders(orders_request_authenticated) 24 | .await?; 25 | println!( 26 | "Orders response authenticated:\n {:?}\n", 27 | orders_response_authenticated 28 | ); 29 | 30 | let orders_request_unauthenticated = tonic::Request::new(GetMyOrdersRequests {}); 31 | let orders_response_unauthenticated = order_client 32 | .get_my_orders(orders_request_unauthenticated) 33 | .await; 34 | println!( 35 | "Orders response unauthenticated:\n {:?}\n", 36 | orders_response_unauthenticated 37 | ); 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /integration_tests/tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | use integration_tests::proto::test_services::protected_service_client::ProtectedServiceClient; 2 | use integration_tests::proto::test_services::protected_service_server::ProtectedServiceServer; 3 | use integration_tests::proto::test_services::public_service_client::PublicServiceClient; 4 | use integration_tests::proto::test_services::public_service_server::PublicServiceServer; 5 | use integration_tests::proto::test_services::{ProtectedMethodRequest, PublicMethodRequest}; 6 | use integration_tests::services::{ 7 | AuthInterceptor, Flow, Interceptor2, Middleware1, ProtectedService, PublicService, 8 | AUTHORIZATION_HEADER_KEY, TOKEN, 9 | }; 10 | use std::str::FromStr; 11 | use std::sync::Arc; 12 | use std::time::Duration; 13 | use tonic::metadata::MetadataValue; 14 | use tonic::transport::{Channel, Endpoint}; 15 | use tonic::Request; 16 | 17 | pub static GRPC_SERVER_ADDRS: &str = "[::1]:50051"; 18 | 19 | pub fn grpc_server_addr() -> String { 20 | "[::1]:50051".to_string() 21 | } 22 | 23 | pub fn grpc_client_connection_url() -> String { 24 | format!("http://{}", grpc_server_addr()) 25 | } 26 | 27 | #[derive(Clone)] 28 | pub struct Services { 29 | pub public_server: Arc>, 30 | pub public_service_client: Arc>, 31 | pub protected_server: Arc>, 32 | pub protected_service_client: Arc>, 33 | pub auth_interceptor: Arc, 34 | pub interceptor2: Arc, 35 | pub middleware1: Arc, 36 | pub flow: Arc, 37 | pub channel: Arc, 38 | } 39 | 40 | impl Services { 41 | pub fn new() -> Self { 42 | let flow = Arc::new(Flow::default()); 43 | let channel = Arc::new( 44 | Endpoint::from_str(grpc_client_connection_url().as_str()) 45 | .expect("endpoint") 46 | .connect_lazy(), 47 | ); 48 | Self { 49 | public_server: Arc::new(PublicServiceServer::new(PublicService::default())), 50 | public_service_client: Arc::new(PublicServiceClient::new(channel.as_ref().clone())), 51 | protected_server: Arc::new(ProtectedServiceServer::new(ProtectedService::default())), 52 | protected_service_client: Arc::new(ProtectedServiceClient::new( 53 | channel.as_ref().clone(), 54 | )), 55 | auth_interceptor: Arc::new(AuthInterceptor::new(flow.clone())), 56 | interceptor2: Arc::new(Interceptor2::new(flow.clone())), 57 | middleware1: Arc::new(Middleware1::new(flow.clone())), 58 | flow, 59 | channel, 60 | } 61 | } 62 | } 63 | 64 | pub fn mk_protected_request() -> Request { 65 | let mut request = Request::new(ProtectedMethodRequest { 66 | message: "Hello!".to_string(), 67 | }); 68 | let token: MetadataValue<_> = TOKEN.parse().expect("token"); 69 | request 70 | .metadata_mut() 71 | .insert(AUTHORIZATION_HEADER_KEY, token); 72 | 73 | request 74 | } 75 | 76 | pub fn mk_public_request() -> Request { 77 | Request::new(PublicMethodRequest { 78 | message: "Hello!".to_string(), 79 | }) 80 | } 81 | 82 | pub async fn sleep() { 83 | tokio::time::sleep(Duration::from_millis(100)).await; 84 | } 85 | -------------------------------------------------------------------------------- /example/src/server.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | pub mod orders; 3 | pub mod products; 4 | pub mod proto; 5 | 6 | use crate::auth::{AuthService, AuthServiceImpl}; 7 | use crate::orders::Orders; 8 | use crate::products::Products; 9 | use crate::proto::estore::order_service_server::OrderServiceServer; 10 | use crate::proto::estore::product_service_server::ProductServiceServer; 11 | use std::net::SocketAddr; 12 | use std::sync::Arc; 13 | use std::time::Instant; 14 | use tonic::body::Body; 15 | use tonic::codegen::http::{HeaderValue, Request, Response}; 16 | use tonic::transport::Server; 17 | use tonic::{async_trait, Status}; 18 | use tonic_middleware::{ 19 | InterceptorFor, Middleware, MiddlewareFor, MiddlewareLayer, RequestInterceptor, 20 | RequestInterceptorLayer, ServiceBound, 21 | }; 22 | 23 | #[tokio::main] 24 | async fn main() -> Result<(), Box> { 25 | let addr: SocketAddr = "[::1]:50051".parse().unwrap(); 26 | 27 | let auth_interceptor = AuthInterceptor { 28 | auth_service: Arc::new(AuthServiceImpl::default()), 29 | }; 30 | 31 | let metrics_middleware = MetricsMiddleware::default(); 32 | 33 | let products_service = Products::default(); 34 | let grpc_products_service = ProductServiceServer::new(products_service); 35 | 36 | let orders_service = Orders::default(); 37 | let grpc_orders_service = OrderServiceServer::new(orders_service); 38 | 39 | println!("Grpc server listening on {}", addr); 40 | 41 | Server::builder() 42 | // Interceptor can be added as a layer so all services will be intercepted 43 | // .layer(RequestInterceptorLayer::new(auth_interceptor.clone())) 44 | // Middleware can also be added as a layer, so it will apply to all services 45 | // .layer(MiddlewareLayer::new(metrics_middleware)) 46 | // Middleware can be added to individual service 47 | .add_service(MiddlewareFor::new( 48 | grpc_products_service, 49 | metrics_middleware, 50 | )) 51 | // Interceptor can be added to individual service as well 52 | .add_service(InterceptorFor::new(grpc_orders_service, auth_interceptor)) 53 | // Middlewares and interceptors can be combined, in any order. 54 | // Outermost will be executed first 55 | // .add_service(MiddlewareFor::new(InterceptorFor::new(grpc_orders_service.clone(), auth_interceptor.clone()), metrics_middleware.clone())) 56 | .serve(addr) 57 | .await?; 58 | Ok(()) 59 | } 60 | 61 | #[derive(Clone)] 62 | pub struct AuthInterceptor { 63 | pub auth_service: Arc, 64 | } 65 | 66 | #[async_trait] 67 | impl RequestInterceptor for AuthInterceptor { 68 | async fn intercept(&self, mut req: Request) -> Result, Status> { 69 | match req.headers().get("authorization").map(|v| v.to_str()) { 70 | Some(Ok(token)) => { 71 | // Get user id from the token 72 | let user_id = self 73 | .auth_service 74 | .verify_token(token) 75 | .await 76 | .map_err(Status::unauthenticated)?; 77 | 78 | // Set user id in header, so it can be used in grpc services through tonic::Request::metadata() 79 | let user_id_header_value = HeaderValue::from_str(&user_id.to_string()) 80 | .map_err(|_e| Status::internal("Failed to convert user_id to header value"))?; 81 | req.headers_mut().insert("user_id", user_id_header_value); 82 | Ok(req) 83 | } 84 | _ => Err(Status::unauthenticated("Unauthenticated")), 85 | } 86 | } 87 | } 88 | 89 | #[derive(Default, Clone)] 90 | pub struct MetricsMiddleware; 91 | 92 | #[async_trait] 93 | impl Middleware for MetricsMiddleware 94 | where 95 | S: ServiceBound, 96 | S::Future: Send, 97 | { 98 | async fn call(&self, req: Request, mut service: S) -> Result, S::Error> { 99 | let start_time = Instant::now(); 100 | // Call the service. You can also intercept request from middleware. 101 | let result = service.call(req).await?; 102 | 103 | let elapsed_time = start_time.elapsed(); 104 | println!("Request processed in {:?}", elapsed_time); 105 | 106 | Ok(result) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/middleware.rs: -------------------------------------------------------------------------------- 1 | use std::task::{Context, Poll}; 2 | 3 | use crate::ServiceBound; 4 | use async_trait::async_trait; 5 | use futures_util::future::BoxFuture; 6 | use tonic::body::Body; 7 | use tonic::codegen::http::Request; 8 | use tonic::codegen::http::Response; 9 | use tonic::codegen::Service; 10 | use tonic::server::NamedService; 11 | use tower::Layer; 12 | 13 | /// The `Middleware` trait defines a generic interface for middleware components 14 | /// in a grpc service chain. 15 | /// Implementors of this trait can modify, observe, or otherwise interact with requests and 16 | /// responses in the service pipeline 17 | /// 18 | /// If you need just intercept requests, pls can [RequestInterceptor] 19 | /// 20 | /// # Type Parameters 21 | /// 22 | /// * `S`: A service bound that defines the requirements for the service being wrapped by 23 | /// the middleware. 24 | /// 25 | /// See [examples on GitHub](https://github.com/teimuraz/tonic-middleware/tree/main/example) 26 | #[async_trait] 27 | pub trait Middleware 28 | where 29 | S: ServiceBound, 30 | { 31 | /// Processes an incoming request and forwards it to the given service. 32 | /// 33 | /// Implementations may perform operations before or after forwarding the request, 34 | /// such as logging, metrics collection, or request modification. 35 | /// 36 | /// # Parameters 37 | /// 38 | /// * `req`: The incoming request to process. 39 | /// * `service`: The service to forward the processed request to. 40 | /// 41 | /// # Returns 42 | /// 43 | /// A `Result` containing the response from the service or an error if one occurred 44 | /// during processing. 45 | async fn call(&self, req: Request, service: S) -> Result, S::Error>; 46 | } 47 | 48 | /// `MiddlewareFor` is a service wrapper that pairs a middleware with its target service. 49 | /// 50 | /// # Type Parameters 51 | /// 52 | /// * `S`: The service that this middleware is wrapping. 53 | /// * `M`: The middleware that is being applied to the service. 54 | #[derive(Clone)] 55 | pub struct MiddlewareFor 56 | where 57 | S: ServiceBound, 58 | M: Middleware, 59 | { 60 | pub inner: S, 61 | pub middleware: M, 62 | } 63 | 64 | impl MiddlewareFor 65 | where 66 | S: ServiceBound, 67 | M: Middleware, 68 | { 69 | /// Constructs a new `MiddlewareFor` with the given service and middleware. 70 | /// 71 | /// # Parameters 72 | /// 73 | /// * `inner`: The service that this middleware is wrapping. 74 | /// * `middleware`: The middleware that is being applied to the service. 75 | pub fn new(inner: S, middleware: M) -> Self { 76 | MiddlewareFor { inner, middleware } 77 | } 78 | } 79 | 80 | impl Service> for MiddlewareFor 81 | where 82 | S: ServiceBound, 83 | S::Future: Send, 84 | M: Middleware + Send + Clone + 'static + Sync, 85 | { 86 | type Response = S::Response; 87 | type Error = S::Error; 88 | type Future = BoxFuture<'static, Result>; 89 | 90 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 91 | self.inner.poll_ready(cx) 92 | } 93 | 94 | fn call(&mut self, req: Request) -> Self::Future { 95 | let middleware = self.middleware.clone(); 96 | let inner = self.inner.clone(); 97 | Box::pin(async move { middleware.call(req, inner).await }) 98 | } 99 | } 100 | 101 | impl NamedService for MiddlewareFor 102 | where 103 | S: NamedService + ServiceBound, 104 | M: Middleware, 105 | { 106 | const NAME: &'static str = S::NAME; 107 | } 108 | 109 | /// `MiddlewareLayer` provides a way to wrap services with a specific middleware using 110 | /// the tower `Layer` trait 111 | #[derive(Clone)] 112 | pub struct MiddlewareLayer { 113 | middleware: M, 114 | } 115 | 116 | impl MiddlewareLayer { 117 | /// Creates a new `MiddlewareLayer` with the given middleware. 118 | /// 119 | /// # Parameters 120 | /// 121 | /// * `middleware`: The middleware to apply to services. 122 | pub fn new(middleware: M) -> Self { 123 | MiddlewareLayer { middleware } 124 | } 125 | } 126 | 127 | impl Layer for MiddlewareLayer 128 | where 129 | S: ServiceBound, 130 | M: Middleware + Clone, 131 | { 132 | type Service = MiddlewareFor; 133 | 134 | fn layer(&self, inner: S) -> Self::Service { 135 | MiddlewareFor::new(inner, self.middleware.clone()) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/request_interceptor.rs: -------------------------------------------------------------------------------- 1 | use std::task::{Context, Poll}; 2 | 3 | use crate::ServiceBound; 4 | use async_trait::async_trait; 5 | use futures_util::future::BoxFuture; 6 | use tonic::body::Body; 7 | use tonic::codegen::http::Request; 8 | use tonic::codegen::Service; 9 | use tonic::server::NamedService; 10 | use tonic::Status; 11 | use tower::Layer; 12 | 13 | /// The `RequestInterceptor` trait is designed to enable the interception and processing of 14 | /// incoming requests within your service pipeline. This trait is particularly useful for 15 | /// performing operations such as authentication, enriching requests with additional metadata, 16 | /// or rejecting requests based on certain criteria before they reach the service logic. 17 | /// 18 | /// If your requirements extend beyond request interception, and you need to interact with both the 19 | /// request and response or to perform actions after the service call has been made, you should 20 | /// consider implementing `Middleware`. 21 | /// 22 | /// See [examples on GitHub](https://github.com/teimuraz/tonic-middleware/tree/main/example) 23 | 24 | #[async_trait] 25 | pub trait RequestInterceptor { 26 | /// Intercepts an incoming request, allowing for inspection, modification, or early rejection 27 | /// with a `Status` error. 28 | /// 29 | /// # Parameters 30 | /// 31 | /// * `req`: The incoming `Request` to be intercepted. 32 | /// 33 | /// # Returns 34 | /// 35 | /// Returns either the potentially modified request for further processing, or a `Status` 36 | /// error to halt processing with a specific error response. 37 | async fn intercept(&self, req: Request) -> Result, Status>; 38 | } 39 | 40 | /// `InterceptorFor` wraps a service with a `RequestInterceptor`, enabling request-level 41 | /// interception before 42 | /// the request reaches the service logic. 43 | /// # Type Parameters 44 | /// 45 | /// * `S`: The service being wrapped. 46 | /// * `I`: The `RequestInterceptor` that will preprocess the requests. 47 | #[derive(Clone)] 48 | pub struct InterceptorFor 49 | where 50 | I: RequestInterceptor, 51 | { 52 | pub inner: S, 53 | pub interceptor: I, 54 | } 55 | 56 | impl InterceptorFor 57 | where 58 | I: RequestInterceptor, 59 | { 60 | /// Creates a new `InterceptorFor` with the provided service and interceptor. 61 | /// 62 | /// # Parameters 63 | /// 64 | /// * `inner`: The service being wrapped. 65 | /// * `interceptor`: The interceptor that will preprocess the requests. 66 | pub fn new(inner: S, interceptor: I) -> Self { 67 | InterceptorFor { inner, interceptor } 68 | } 69 | } 70 | 71 | impl Service> for InterceptorFor 72 | where 73 | S: ServiceBound, 74 | S::Future: Send, 75 | I: RequestInterceptor + Send + Clone + 'static + Sync, 76 | { 77 | type Response = S::Response; 78 | type Error = S::Error; 79 | type Future = BoxFuture<'static, Result>; 80 | 81 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 82 | self.inner.poll_ready(cx) 83 | } 84 | 85 | fn call(&mut self, req: Request) -> Self::Future { 86 | let interceptor = self.interceptor.clone(); 87 | let mut inner = self.inner.clone(); 88 | Box::pin(async move { 89 | match interceptor.intercept(req).await { 90 | Ok(req) => inner.call(req).await, 91 | Err(status) => { 92 | let response = status.into_http(); 93 | Ok(response) 94 | } 95 | } 96 | }) 97 | } 98 | } 99 | 100 | impl NamedService for InterceptorFor 101 | where 102 | S: NamedService, 103 | I: RequestInterceptor, 104 | { 105 | const NAME: &'static str = S::NAME; 106 | } 107 | 108 | /// `RequestInterceptorLayer` provides a way to wrap services with a specific interceptor using the tower `Layer` trait 109 | /// 110 | /// # Type Parameters 111 | /// 112 | /// * `I`: The `RequestInterceptor` implementation. 113 | #[derive(Clone)] 114 | pub struct RequestInterceptorLayer { 115 | interceptor: I, 116 | } 117 | 118 | impl RequestInterceptorLayer { 119 | /// Creates a new `RequestInterceptorLayer` with the given interceptor. 120 | /// 121 | /// # Parameters 122 | /// 123 | /// * `interceptor`: The interceptor to apply to services. 124 | pub fn new(interceptor: I) -> Self { 125 | RequestInterceptorLayer { interceptor } 126 | } 127 | } 128 | 129 | impl Layer for RequestInterceptorLayer 130 | where 131 | I: RequestInterceptor + Clone, 132 | { 133 | type Service = InterceptorFor; 134 | 135 | fn layer(&self, inner: S) -> Self::Service { 136 | InterceptorFor::new(inner, self.interceptor.clone()) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /integration_tests/src/services.rs: -------------------------------------------------------------------------------- 1 | use crate::proto::test_services::protected_service_server::ProtectedService as GrpcProtectedService; 2 | use crate::proto::test_services::public_service_server::PublicService as GrpcPublicService; 3 | use crate::proto::test_services::{ 4 | ProtectedMethodRequest, ProtectedMethodResponse, PublicMethodRequest, PublicMethodResponse, 5 | }; 6 | use std::sync::{Arc, Mutex}; 7 | use tonic::body::Body; 8 | use tonic::codegen::http::HeaderValue; 9 | use tonic::{async_trait, Request, Response, Status}; 10 | use tonic_middleware::{Middleware, RequestInterceptor, ServiceBound}; 11 | 12 | pub static USER_ID_HEADER_KEY: &str = "user_id"; 13 | pub static USER_ID: &str = "user-1"; 14 | pub static AUTHORIZATION_HEADER_KEY: &str = "authorization"; 15 | pub static TOKEN: &str = "supersecret"; 16 | 17 | #[derive(Clone, Default)] 18 | pub struct PublicService; 19 | 20 | #[async_trait] 21 | impl GrpcPublicService for PublicService { 22 | async fn public_method( 23 | &self, 24 | _request: Request, 25 | ) -> Result, Status> { 26 | Ok(Response::new(PublicMethodResponse { 27 | message: "Hello Public!".to_string(), 28 | })) 29 | } 30 | } 31 | 32 | #[derive(Clone, Default)] 33 | pub struct ProtectedService {} 34 | 35 | #[async_trait] 36 | impl GrpcProtectedService for ProtectedService { 37 | async fn protected_method( 38 | &self, 39 | request: Request, 40 | ) -> Result, Status> { 41 | let user_id = request 42 | .metadata() 43 | .get(USER_ID_HEADER_KEY) 44 | .map(|a| a.to_str().expect("Valid user_id").to_string()) 45 | .expect("Actions to be defined"); 46 | Ok(Response::new(ProtectedMethodResponse { 47 | message: "Hello Protected!".to_string(), 48 | user_id, 49 | })) 50 | } 51 | } 52 | 53 | #[derive(Clone)] 54 | pub struct AuthInterceptor { 55 | pub flow: Arc, 56 | } 57 | 58 | #[async_trait] 59 | impl RequestInterceptor for AuthInterceptor { 60 | async fn intercept( 61 | &self, 62 | mut req: tonic::codegen::http::Request, 63 | ) -> Result, Status> { 64 | self.flow.add_action(Action::AuthInterceptor); 65 | match req 66 | .headers() 67 | .get(AUTHORIZATION_HEADER_KEY) 68 | .map(|v| v.to_str()) 69 | { 70 | Some(Ok(token)) => { 71 | if token != TOKEN { 72 | Err(Status::unauthenticated("Unauthenticated")) 73 | } else { 74 | let user_id = HeaderValue::from_str(USER_ID) 75 | .map_err(|_e| Status::internal("Failed set header value"))?; 76 | req.headers_mut().insert(USER_ID_HEADER_KEY, user_id); 77 | 78 | Ok(req) 79 | } 80 | } 81 | _ => Err(Status::unauthenticated("Unauthenticated")), 82 | } 83 | } 84 | } 85 | 86 | impl AuthInterceptor { 87 | pub fn new(flow: Arc) -> Self { 88 | Self { flow } 89 | } 90 | } 91 | 92 | #[derive(Clone)] 93 | pub struct Interceptor2 { 94 | pub flow: Arc, 95 | } 96 | 97 | #[async_trait] 98 | impl RequestInterceptor for Interceptor2 { 99 | async fn intercept( 100 | &self, 101 | req: tonic::codegen::http::Request, 102 | ) -> Result, Status> { 103 | self.flow.add_action(Action::Interceptor2); 104 | Ok(req) 105 | } 106 | } 107 | 108 | impl Interceptor2 { 109 | pub fn new(flow: Arc) -> Self { 110 | Self { flow } 111 | } 112 | } 113 | 114 | #[derive(Clone)] 115 | pub struct Middleware1 { 116 | pub flow: Arc, 117 | } 118 | 119 | #[async_trait] 120 | impl Middleware for Middleware1 121 | where 122 | S: ServiceBound, 123 | S::Future: Send, 124 | { 125 | async fn call( 126 | &self, 127 | req: tonic::codegen::http::Request, 128 | mut service: S, 129 | ) -> Result, S::Error> { 130 | self.flow.add_action(Action::Middleware1Before); 131 | let result = service.call(req).await?; 132 | self.flow.add_action(Action::Middleware1After); 133 | Ok(result) 134 | } 135 | } 136 | 137 | impl Middleware1 { 138 | pub fn new(flow: Arc) -> Self { 139 | Self { flow } 140 | } 141 | } 142 | 143 | #[derive(Debug, Clone, PartialEq)] 144 | pub enum Action { 145 | AuthInterceptor, 146 | Interceptor2, 147 | Middleware1Before, 148 | Middleware1After, 149 | } 150 | 151 | #[derive(Clone, Default)] 152 | pub struct Flow { 153 | pub actions: Arc>>, 154 | } 155 | 156 | impl Flow { 157 | pub fn add_action(&self, action: Action) { 158 | let mut actions = self.actions.lock().unwrap(); 159 | actions.push(action); 160 | } 161 | 162 | pub fn read_actions(&self) -> Vec { 163 | let actions = self.actions.lock().unwrap(); 164 | actions.clone() 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tonic-middleware 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/tonic-middleware)](https://crates.io/crates/tonic-middleware) 4 | [![Documentation](https://docs.rs/tonic-middleware/badge.svg)](https://docs.rs/tonic-middleware/latest/tonic_middleware) 5 | [![Crates.io](https://img.shields.io/crates/l/tonic-middleware)](LICENSE) 6 | 7 | ## Table of Contents 8 | - [Introduction](#introduction) 9 | - [Tonic versions compatability](#tonic-versions-compatability) 10 | - [Usage](#usage) 11 | - [Define request interceptor and middleware](#define-our-request-interceptor-and-middleware) 12 | - [Apply request interceptor to individual service](#apply-request-interceptor-to-individual-service) 13 | - [Apply request interceptor to all services using layer](#apply-request-interceptor-to-all-services-using-layer) 14 | - [Apply middleware to individual services](#apply-middleware-to-individual-services) 15 | - [Apply middleware to all services through layer](#apply-middleware-to-all-services-through-layer) 16 | - [Combine interceptor and middleware for individual services](#combine-interceptor-and-middleware-for-individual-services) 17 | - [Apply interceptor and middleware to all services through layer](#apply-interceptor-and-middleware-to-all-services-through-layer) 18 | - Full [example](https://github.com/teimuraz/tonic-middleware/tree/main/example) or check [integration tests](https://github.com/teimuraz/tonic-middleware/blob/main/integration_tests/tests/tests.rs) 19 | - [Motivation](#motivation) 20 | 21 | # Introduction 22 | 23 | `tonic-middleware` is a Rust library that extends [tonic](https://github.com/hyperium/tonic)-based [gRPC](https://grpc.io/) services, 24 | enabling **asynchronous** inspection and modification and potentially rejecting of incoming requests. 25 | It also enables the addition of custom logic through middleware, both before and after the actual service call. 26 | 27 | The library provides two key tools: 28 | 29 | - **Request Interceptor** 30 | 31 | The `RequestInterceptor` trait is designed to enable the interception and processing of 32 | incoming requests within your service pipeline. This trait is particularly useful for 33 | performing operations such as authentication, enriching requests with additional metadata, 34 | or rejecting requests based on certain criteria before they reach the service logic. 35 | 36 | 37 | - **Middleware** 38 | 39 | If your requirements extend beyond request interception, and you need to interact with both the 40 | request and response or to perform actions after the service call has been made, you should 41 | consider implementing `Middleware`. 42 | 43 | Both interceptors and middlewares can be applied to individual service, or to all services 44 | through Tonic's layer. 45 | 46 | ## Tonic versions compatability 47 | 48 | | tonic version | tonic-middleware version | Notes | 49 | |---------------|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 50 | | 0.11 | 0.1.4 | | 51 | | 0.12.x | 0.2.x | Breaking changes
resulting from breaking changes in tonic.
See [changelog](https://github.com/teimuraz/tonic-middleware/releases/tag/v0.2.0) for more details. | 52 | | 0.13.x | 0.3.x | Breaking changes
resulting from breaking changes in tonic. | 53 | | 0.14.x | 0.4.x | Breaking changes
resulting from breaking changes in tonic.
See [changelog](https://github.com/hyperium/tonic/releases/tag/v0.14.0) for more details. | 54 | 55 | 56 | ## Usage 57 | 58 | Add to Cargo.toml 59 | ``` 60 | tonic-middleware = "0.4.0" 61 | ``` 62 | 63 | See full [example](https://github.com/teimuraz/tonic-middleware/tree/main/example) or check [integration tests](https://github.com/teimuraz/tonic-middleware/blob/main/integration_tests/tests/tests.rs) 64 | 65 | 66 | ### Define our request interceptor and middleware 67 | 68 | #### To create request interceptor, we need to implement `RequestInterceptor` trait from the library. 69 | ### Note: 70 | > Please use `tonic::codegen::http::{Request, Response}` (which are just re-exported from `http` crate by tonic, i.e. `http:{Request, Response}`) 71 | > instead of `tonic::{Request, Response}` in interceptors and middlewares. 72 | 73 | 74 | Simple request interceptor that uses some custom `AuthService` injected in to perform authentication. 75 | We need to implement `RequestInterceptor` for our custom (`AuthInterceptor`) intercept. 76 | ```rust 77 | use tonic::codegen::http::Request; // Use this instead of tonic::Request in Interceptor! 78 | use tonic::codegen::http::Response; // Use this instead of tonic::Response in Interceptor! 79 | ... 80 | 81 | #[derive(Clone)] 82 | pub struct AuthInterceptor { 83 | pub auth_service: A, 84 | } 85 | 86 | #[async_trait] 87 | impl RequestInterceptor for AuthInterceptor
{ 88 | async fn intercept(&self, mut req: Request) -> Result, Status> { 89 | match req.headers().get("authorization").map(|v| v.to_str()) { 90 | Some(Ok(token)) => { 91 | // Get user id from the token 92 | let user_id = self 93 | .auth_service 94 | .verify_token(token) 95 | .await 96 | .map_err(Status::unauthenticated)?; 97 | 98 | // Set user id in header, so it can be used in grpc services through tonic::Request::metadata() 99 | let user_id_header_value = HeaderValue::from_str(&user_id.to_string()) 100 | .map_err(|_e| Status::internal("Failed to convert user_id to header value"))?; 101 | req.headers_mut().insert("user_id", user_id_header_value); 102 | Ok(req) 103 | } 104 | _ => Err(Status::unauthenticated("Unauthenticated")), 105 | } 106 | } 107 | } 108 | ``` 109 | 110 | #### To create middleware, we need to implement 'Middleware' trait from the library. 111 | 112 | Metrics middleware that measures request time and output to stdout. 113 | We need to implement `Middleware` for our custom (`MetricsMiddleware`) middleware. 114 | ```rust 115 | use tonic::codegen::http::Request; // Use this instead of tonic::Request in Middleware! 116 | use tonic::codegen::http::Response; // Use this instead of tonic::Response in Middleware! 117 | ... 118 | 119 | #[derive(Default, Clone)] 120 | pub struct MetricsMiddleware; 121 | 122 | #[async_trait] 123 | impl Middleware for MetricsMiddleware 124 | where 125 | S: ServiceBound, 126 | S::Future: Send, 127 | { 128 | async fn call( 129 | &self, 130 | req: Request, 131 | mut service: S, 132 | ) -> Result, S::Error> { 133 | let start_time = Instant::now(); 134 | // Call the service. You can also intercept request from middleware. 135 | let result = service.call(req).await?; 136 | 137 | let elapsed_time = start_time.elapsed(); 138 | println!("Request processed in {:?}", elapsed_time); 139 | 140 | Ok(result) 141 | } 142 | } 143 | 144 | ``` 145 | 146 | ### Apply request interceptor to individual service 147 | ```rust 148 | #[tokio::main] 149 | async fn main() -> Result<(), Box> { 150 | let addr: SocketAddr = "[::1]:50051".parse().unwrap(); 151 | 152 | let auth_interceptor = AuthInterceptor { 153 | auth_service: AuthServiceImpl::default(), 154 | }; 155 | 156 | // Grpc service 157 | let products_service = Products::default(); 158 | let grpc_products_service = ProductServiceServer::new(products_service); 159 | 160 | // Grpc service 161 | let orders_service = Orders::default(); 162 | let grpc_orders_service = OrderServiceServer::new(orders_service); 163 | 164 | println!("Grpc server listening on {}", addr); 165 | 166 | Server::builder() 167 | // No interceptor applied 168 | .add_service(grpc_products_service) 169 | // Added interceptor to single service 170 | .add_service(InterceptorFor::new(grpc_orders_service, auth_interceptor)) 171 | .serve(addr) 172 | .await?; 173 | // ... 174 | } 175 | ``` 176 | 177 | ### Apply request interceptor to all services using layer 178 | ```rust 179 | #[tokio::main] 180 | async fn main() -> Result<(), Box> { 181 | 182 | // ... 183 | Server::builder() 184 | // Interceptor can be added as a layer so all services will be intercepted 185 | .layer(RequestInterceptorLayer::new(auth_interceptor.clone())) 186 | .add_service(grpc_products_service) 187 | .add_service(grpc_orders_service) 188 | .serve(addr) 189 | .await?; 190 | // ... 191 | } 192 | ``` 193 | 194 | ### Apply middleware to individual services 195 | ```rust 196 | #[tokio::main] 197 | async fn main() -> Result<(), Box> { 198 | 199 | // ... 200 | Server::builder() 201 | // Middleware can be added to individual service 202 | .add_service(MiddlewareFor::new( 203 | grpc_products_service, 204 | metrics_middleware, 205 | )) 206 | // No middleware applied 207 | .add_service(grpc_orders_service) 208 | 209 | .serve(addr) 210 | .await?; 211 | // ... 212 | } 213 | ``` 214 | 215 | ### Apply middleware to all services through layer 216 | ```rust 217 | #[tokio::main] 218 | async fn main() -> Result<(), Box> { 219 | 220 | // ... 221 | Server::builder() 222 | // Middleware can also be added as a layer, so it will apply to 223 | // all services 224 | .layer(MiddlewareLayer::new(metrics_middleware)) 225 | 226 | .add_service(grpc_products_service) 227 | .add_service(grpc_orders_service) 228 | .serve(addr) 229 | .await?; 230 | // ... 231 | } 232 | ``` 233 | 234 | ### Combine interceptor and middleware for individual services 235 | 236 | ```rust 237 | #[tokio::main] 238 | async fn main() -> Result<(), Box> { 239 | 240 | // ... 241 | Server::builder() 242 | // Middlewares and interceptors can be combined, in any order. 243 | // Outermost will be executed first 244 | .add_service( 245 | MiddlewareFor::new( 246 | InterceptorFor::new(grpc_orders_service.clone(), auth_interceptor.clone()), 247 | metrics_middleware.clone(), 248 | )) 249 | .add_service(grpc_products_service) 250 | .await?; 251 | // ... 252 | } 253 | ``` 254 | 255 | ### Apply interceptor and middleware to all services through layer 256 | ```rust 257 | #[tokio::main] 258 | async fn main() -> Result<(), Box> { 259 | 260 | // ... 261 | Server::builder() 262 | // Interceptor can be added as a layer so all services will be intercepted 263 | .layer(RequestInterceptorLayer::new(auth_interceptor.clone())) 264 | // Middleware can also be added as a layer, so it will apply to all services 265 | .layer(MiddlewareLayer::new(metrics_middleware)) 266 | 267 | .add_service(grpc_products_service) 268 | .add_service(grpc_orders_service) 269 | .await?; 270 | // ... 271 | } 272 | ``` 273 | 274 | 275 | 276 | ## Motivation 277 | Tonic provides a solid foundation for developing gRPC services in Rust, and while it offers a range of features, extending it with asynchronous interceptors and middleware requires a bit more effort. That's where `tonic-middleware` comes in, 278 | this library simplifies adding custom asynchronous processing to the [tonic](https://github.com/hyperium/tonic) service stack. 279 | -------------------------------------------------------------------------------- /integration_tests/tests/tests.rs: -------------------------------------------------------------------------------- 1 | pub mod common; 2 | 3 | use integration_tests::proto; 4 | 5 | use crate::common::{grpc_server_addr, mk_protected_request, mk_public_request, sleep, Services}; 6 | use crate::proto::test_services::ProtectedMethodRequest; 7 | use integration_tests::services::{Action, USER_ID}; 8 | use serial_test::serial; 9 | use tokio::sync::oneshot; 10 | use tonic::transport::Server; 11 | use tonic::Code; 12 | use tonic_middleware::{InterceptorFor, MiddlewareFor, MiddlewareLayer, RequestInterceptorLayer}; 13 | 14 | #[tokio::test] 15 | #[serial] 16 | async fn test_interceptor_applies_to_individual_service_rejecting_request() { 17 | let services = Services::new(); 18 | let public_server = services.public_server.as_ref().clone(); 19 | let protected_server = services.protected_server.as_ref().clone(); 20 | let auth_interceptor = services.auth_interceptor.as_ref().clone(); 21 | let flow = services.flow; 22 | 23 | let (tx, rx) = oneshot::channel(); 24 | let jh = tokio::spawn(async move { 25 | Server::builder() 26 | .add_service(public_server) 27 | .add_service(InterceptorFor::new(protected_server, auth_interceptor)) 28 | .serve_with_shutdown(grpc_server_addr().parse().unwrap(), async { 29 | drop(rx.await) 30 | }) 31 | .await 32 | .unwrap() 33 | }); 34 | 35 | sleep().await; 36 | 37 | let mut public_service_client = services.public_service_client.as_ref().clone(); 38 | public_service_client 39 | .public_method(mk_public_request()) 40 | .await 41 | .expect("Public method response"); 42 | 43 | let result = services 44 | .protected_service_client 45 | .as_ref() 46 | .clone() 47 | .protected_method(ProtectedMethodRequest { 48 | message: "Hello!".to_string(), 49 | }) 50 | .await; 51 | 52 | assert!(result.is_err_and(|e| e.code() == Code::Unauthenticated)); 53 | 54 | let actions: Vec = flow.read_actions(); 55 | assert_eq!(actions.len(), 1); 56 | assert_eq!(actions[0], Action::AuthInterceptor); 57 | 58 | tx.send(()).unwrap(); 59 | jh.await.unwrap(); 60 | } 61 | 62 | #[tokio::test] 63 | #[serial] 64 | async fn test_interceptor_applies_to_individual_service_and_sets_request_header() { 65 | let services = Services::new(); 66 | let public_server = services.public_server.as_ref().clone(); 67 | let protected_server = services.protected_server.as_ref().clone(); 68 | let auth_interceptor = services.auth_interceptor.as_ref().clone(); 69 | let flow = services.flow; 70 | 71 | let (tx, rx) = oneshot::channel(); 72 | let jh = tokio::spawn(async move { 73 | Server::builder() 74 | .add_service(public_server) 75 | .add_service(InterceptorFor::new(protected_server, auth_interceptor)) 76 | .serve_with_shutdown(grpc_server_addr().parse().unwrap(), async { 77 | drop(rx.await) 78 | }) 79 | .await 80 | .unwrap() 81 | }); 82 | 83 | let mut protected_service_client = services.protected_service_client.as_ref().clone(); 84 | 85 | sleep().await; 86 | 87 | let request = mk_protected_request(); 88 | let result = protected_service_client 89 | .protected_method(request) 90 | .await 91 | .expect("Method response"); 92 | 93 | assert_eq!(result.get_ref().user_id, USER_ID); 94 | 95 | let actions: Vec = flow.read_actions(); 96 | assert_eq!(actions.len(), 1); 97 | assert_eq!(actions[0], Action::AuthInterceptor); 98 | 99 | tx.send(()).unwrap(); 100 | jh.await.unwrap(); 101 | } 102 | 103 | #[tokio::test] 104 | #[serial] 105 | async fn test_interceptor_applies_to_all_services() { 106 | let services = Services::new(); 107 | let public_server = services.public_server.as_ref().clone(); 108 | let protected_server = services.protected_server.as_ref().clone(); 109 | let auth_interceptor = services.auth_interceptor.as_ref().clone(); 110 | let interceptor2 = services.interceptor2.as_ref().clone(); 111 | let flow = services.flow; 112 | 113 | let (tx, rx) = oneshot::channel(); 114 | let jh = tokio::spawn(async move { 115 | Server::builder() 116 | .layer(RequestInterceptorLayer::new(interceptor2)) 117 | .add_service(public_server) 118 | .add_service(InterceptorFor::new(protected_server, auth_interceptor)) 119 | .serve_with_shutdown(grpc_server_addr().parse().unwrap(), async { 120 | drop(rx.await) 121 | }) 122 | .await 123 | .unwrap() 124 | }); 125 | 126 | let mut protected_service_client = services.protected_service_client.as_ref().clone(); 127 | 128 | sleep().await; 129 | 130 | let request = mk_protected_request(); 131 | let result = protected_service_client 132 | .protected_method(request) 133 | .await 134 | .expect("Method response"); 135 | 136 | assert_eq!(result.get_ref().user_id, USER_ID); 137 | 138 | let mut public_service_client = services.public_service_client.as_ref().clone(); 139 | public_service_client 140 | .public_method(mk_public_request()) 141 | .await 142 | .expect("Public method response"); 143 | 144 | let actions: Vec = flow.read_actions(); 145 | assert_eq!(actions.len(), 3); 146 | // protected_service_client call -> interceptor2 through layer 147 | assert_eq!(actions[0], Action::Interceptor2); 148 | // protected_service_client call -> auth_interceptor through layer 149 | assert_eq!(actions[1], Action::AuthInterceptor); 150 | // public_service_client call -> interceptor2 through layer 151 | assert_eq!(actions[2], Action::Interceptor2); 152 | 153 | tx.send(()).unwrap(); 154 | jh.await.unwrap(); 155 | } 156 | 157 | #[tokio::test] 158 | #[serial] 159 | async fn test_interceptors_can_be_combined() { 160 | let services = Services::new(); 161 | let public_server = services.public_server.as_ref().clone(); 162 | let protected_server = services.protected_server.as_ref().clone(); 163 | let auth_interceptor = services.auth_interceptor.as_ref().clone(); 164 | let interceptor2 = services.interceptor2.as_ref().clone(); 165 | let flow = services.flow; 166 | 167 | let (tx, rx) = oneshot::channel(); 168 | let jh = tokio::spawn(async move { 169 | Server::builder() 170 | .add_service(public_server) 171 | .add_service(InterceptorFor::new( 172 | InterceptorFor::new(protected_server, auth_interceptor), 173 | interceptor2, 174 | )) 175 | .serve_with_shutdown(grpc_server_addr().parse().unwrap(), async { 176 | drop(rx.await) 177 | }) 178 | .await 179 | .unwrap() 180 | }); 181 | 182 | let mut protected_service_client = services.protected_service_client.as_ref().clone(); 183 | 184 | sleep().await; 185 | 186 | let request = mk_protected_request(); 187 | let result = protected_service_client 188 | .protected_method(request) 189 | .await 190 | .expect("Method response"); 191 | 192 | assert_eq!(result.get_ref().user_id, USER_ID); 193 | 194 | let actions: Vec = flow.read_actions(); 195 | assert_eq!(actions.len(), 2); 196 | assert_eq!(actions[0], Action::Interceptor2); 197 | assert_eq!(actions[1], Action::AuthInterceptor); 198 | 199 | tx.send(()).unwrap(); 200 | jh.await.unwrap(); 201 | } 202 | 203 | #[tokio::test] 204 | #[serial] 205 | async fn test_middleware_applied_to_individual_service() { 206 | let services = Services::new(); 207 | let public_server = services.public_server.as_ref().clone(); 208 | let protected_server = services.protected_server.as_ref().clone(); 209 | let middleware1 = services.middleware1.as_ref().clone(); 210 | let auth_interceptor = services.auth_interceptor.as_ref().clone(); 211 | let flow = services.flow; 212 | 213 | let (tx, rx) = oneshot::channel(); 214 | let jh = tokio::spawn(async move { 215 | Server::builder() 216 | .add_service(MiddlewareFor::new(public_server, middleware1)) 217 | .add_service(InterceptorFor::new(protected_server, auth_interceptor)) 218 | .serve_with_shutdown(grpc_server_addr().parse().unwrap(), async { 219 | drop(rx.await) 220 | }) 221 | .await 222 | .unwrap() 223 | }); 224 | 225 | let mut protected_service_client = services.protected_service_client.as_ref().clone(); 226 | 227 | sleep().await; 228 | 229 | let request = mk_protected_request(); 230 | let result = protected_service_client 231 | .protected_method(request) 232 | .await 233 | .expect("Method response"); 234 | 235 | assert_eq!(result.get_ref().user_id, USER_ID); 236 | 237 | services 238 | .public_service_client 239 | .as_ref() 240 | .clone() 241 | .public_method(mk_public_request()) 242 | .await 243 | .expect("Method response"); 244 | 245 | let actions: Vec = flow.read_actions(); 246 | assert_eq!(actions.len(), 3); 247 | assert_eq!(actions[0], Action::AuthInterceptor); 248 | assert_eq!(actions[1], Action::Middleware1Before); 249 | assert_eq!(actions[2], Action::Middleware1After); 250 | 251 | tx.send(()).unwrap(); 252 | jh.await.unwrap(); 253 | } 254 | 255 | #[tokio::test] 256 | #[serial] 257 | async fn test_middleware_applied_to_all_services_through_layer() { 258 | let services = Services::new(); 259 | let public_server = services.public_server.as_ref().clone(); 260 | let protected_server = services.protected_server.as_ref().clone(); 261 | let middleware1 = services.middleware1.as_ref().clone(); 262 | let auth_interceptor = services.auth_interceptor.as_ref().clone(); 263 | let flow = services.flow; 264 | 265 | let (tx, rx) = oneshot::channel(); 266 | let jh = tokio::spawn(async move { 267 | Server::builder() 268 | .layer(MiddlewareLayer::new(middleware1)) 269 | .add_service(public_server) 270 | .add_service(InterceptorFor::new(protected_server, auth_interceptor)) 271 | .serve_with_shutdown(grpc_server_addr().parse().unwrap(), async { 272 | drop(rx.await) 273 | }) 274 | .await 275 | .unwrap() 276 | }); 277 | 278 | let mut protected_service_client = services.protected_service_client.as_ref().clone(); 279 | 280 | sleep().await; 281 | 282 | let request = mk_protected_request(); 283 | let result = protected_service_client 284 | .protected_method(request) 285 | .await 286 | .expect("Method response"); 287 | 288 | assert_eq!(result.get_ref().user_id, USER_ID); 289 | 290 | services 291 | .public_service_client 292 | .as_ref() 293 | .clone() 294 | .public_method(mk_public_request()) 295 | .await 296 | .expect("Method response"); 297 | 298 | let actions: Vec = flow.read_actions(); 299 | assert_eq!(actions.len(), 5); 300 | assert_eq!(actions[0], Action::Middleware1Before); 301 | assert_eq!(actions[1], Action::AuthInterceptor); 302 | assert_eq!(actions[2], Action::Middleware1After); 303 | assert_eq!(actions[3], Action::Middleware1Before); 304 | assert_eq!(actions[4], Action::Middleware1After); 305 | 306 | tx.send(()).unwrap(); 307 | jh.await.unwrap(); 308 | } 309 | 310 | #[tokio::test] 311 | #[serial] 312 | async fn test_interceptor_and_middleware_combined_for_individual_service() { 313 | let services = Services::new(); 314 | let public_server = services.public_server.as_ref().clone(); 315 | let protected_server = services.protected_server.as_ref().clone(); 316 | let middleware1 = services.middleware1.as_ref().clone(); 317 | let auth_interceptor = services.auth_interceptor.as_ref().clone(); 318 | let flow = services.flow; 319 | 320 | let (tx, rx) = oneshot::channel(); 321 | let jh = tokio::spawn(async move { 322 | Server::builder() 323 | .add_service(public_server) 324 | .add_service(MiddlewareFor::new( 325 | InterceptorFor::new(protected_server, auth_interceptor), 326 | middleware1, 327 | )) 328 | .serve_with_shutdown(grpc_server_addr().parse().unwrap(), async { 329 | drop(rx.await) 330 | }) 331 | .await 332 | .unwrap() 333 | }); 334 | 335 | let mut protected_service_client = services.protected_service_client.as_ref().clone(); 336 | 337 | sleep().await; 338 | 339 | let request = mk_protected_request(); 340 | let result = protected_service_client 341 | .protected_method(request) 342 | .await 343 | .expect("Method response"); 344 | 345 | assert_eq!(result.get_ref().user_id, USER_ID); 346 | 347 | services 348 | .public_service_client 349 | .as_ref() 350 | .clone() 351 | .public_method(mk_public_request()) 352 | .await 353 | .expect("Method response"); 354 | 355 | let actions: Vec = flow.read_actions(); 356 | assert_eq!(actions.len(), 3); 357 | assert_eq!(actions[0], Action::Middleware1Before); 358 | assert_eq!(actions[1], Action::AuthInterceptor); 359 | assert_eq!(actions[2], Action::Middleware1After); 360 | 361 | tx.send(()).unwrap(); 362 | jh.await.unwrap(); 363 | } 364 | 365 | #[tokio::test] 366 | #[serial] 367 | async fn test_interceptor_and_middleware_combined_for_all_services_through_layer() { 368 | let services = Services::new(); 369 | let public_server = services.public_server.as_ref().clone(); 370 | let protected_server = services.protected_server.as_ref().clone(); 371 | let middleware1 = services.middleware1.as_ref().clone(); 372 | let auth_interceptor = services.auth_interceptor.as_ref().clone(); 373 | let interceptor2 = services.interceptor2.as_ref().clone(); 374 | let flow = services.flow; 375 | 376 | let (tx, rx) = oneshot::channel(); 377 | let jh = tokio::spawn(async move { 378 | Server::builder() 379 | .layer(MiddlewareLayer::new(middleware1)) 380 | .layer(RequestInterceptorLayer::new(interceptor2)) 381 | .add_service(public_server) 382 | .add_service(InterceptorFor::new(protected_server, auth_interceptor)) 383 | .serve_with_shutdown(grpc_server_addr().parse().unwrap(), async { 384 | drop(rx.await) 385 | }) 386 | .await 387 | .unwrap() 388 | }); 389 | 390 | let mut protected_service_client = services.protected_service_client.as_ref().clone(); 391 | 392 | sleep().await; 393 | 394 | let request = mk_protected_request(); 395 | protected_service_client 396 | .protected_method(request) 397 | .await 398 | .expect("Method response"); 399 | 400 | services 401 | .public_service_client 402 | .as_ref() 403 | .clone() 404 | .public_method(mk_public_request()) 405 | .await 406 | .expect("Method response"); 407 | 408 | let actions: Vec = flow.read_actions(); 409 | assert_eq!(actions.len(), 7); 410 | assert_eq!(actions[0], Action::Middleware1Before); 411 | assert_eq!(actions[1], Action::Interceptor2); 412 | assert_eq!(actions[2], Action::AuthInterceptor); 413 | assert_eq!(actions[3], Action::Middleware1After); 414 | assert_eq!(actions[4], Action::Middleware1Before); 415 | assert_eq!(actions[5], Action::Interceptor2); 416 | assert_eq!(actions[6], Action::Middleware1After); 417 | 418 | tx.send(()).unwrap(); 419 | jh.await.unwrap(); 420 | } 421 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anyhow" 31 | version = "1.0.98" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 34 | 35 | [[package]] 36 | name = "async-trait" 37 | version = "0.1.88" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" 40 | dependencies = [ 41 | "proc-macro2", 42 | "quote", 43 | "syn", 44 | ] 45 | 46 | [[package]] 47 | name = "atomic-waker" 48 | version = "1.1.2" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 51 | 52 | [[package]] 53 | name = "autocfg" 54 | version = "1.5.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 57 | 58 | [[package]] 59 | name = "axum" 60 | version = "0.8.4" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" 63 | dependencies = [ 64 | "axum-core", 65 | "bytes", 66 | "futures-util", 67 | "http", 68 | "http-body", 69 | "http-body-util", 70 | "itoa", 71 | "matchit", 72 | "memchr", 73 | "mime", 74 | "percent-encoding", 75 | "pin-project-lite", 76 | "rustversion", 77 | "serde", 78 | "sync_wrapper", 79 | "tower", 80 | "tower-layer", 81 | "tower-service", 82 | ] 83 | 84 | [[package]] 85 | name = "axum-core" 86 | version = "0.5.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" 89 | dependencies = [ 90 | "bytes", 91 | "futures-core", 92 | "http", 93 | "http-body", 94 | "http-body-util", 95 | "mime", 96 | "pin-project-lite", 97 | "rustversion", 98 | "sync_wrapper", 99 | "tower-layer", 100 | "tower-service", 101 | ] 102 | 103 | [[package]] 104 | name = "backtrace" 105 | version = "0.3.75" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 108 | dependencies = [ 109 | "addr2line", 110 | "cfg-if", 111 | "libc", 112 | "miniz_oxide", 113 | "object", 114 | "rustc-demangle", 115 | "windows-targets", 116 | ] 117 | 118 | [[package]] 119 | name = "base64" 120 | version = "0.22.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 123 | 124 | [[package]] 125 | name = "bitflags" 126 | version = "2.9.1" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 129 | 130 | [[package]] 131 | name = "bytes" 132 | version = "1.10.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 135 | 136 | [[package]] 137 | name = "cfg-if" 138 | version = "1.0.1" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 141 | 142 | [[package]] 143 | name = "either" 144 | version = "1.15.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 147 | 148 | [[package]] 149 | name = "equivalent" 150 | version = "1.0.2" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 153 | 154 | [[package]] 155 | name = "errno" 156 | version = "0.3.13" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" 159 | dependencies = [ 160 | "libc", 161 | "windows-sys", 162 | ] 163 | 164 | [[package]] 165 | name = "example" 166 | version = "0.1.0" 167 | dependencies = [ 168 | "prost", 169 | "tokio", 170 | "tonic", 171 | "tonic-middleware", 172 | "tonic-prost", 173 | "tonic-prost-build", 174 | ] 175 | 176 | [[package]] 177 | name = "fastrand" 178 | version = "2.3.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 181 | 182 | [[package]] 183 | name = "fixedbitset" 184 | version = "0.5.7" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" 187 | 188 | [[package]] 189 | name = "fnv" 190 | version = "1.0.7" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 193 | 194 | [[package]] 195 | name = "futures" 196 | version = "0.3.31" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 199 | dependencies = [ 200 | "futures-channel", 201 | "futures-core", 202 | "futures-executor", 203 | "futures-io", 204 | "futures-sink", 205 | "futures-task", 206 | "futures-util", 207 | ] 208 | 209 | [[package]] 210 | name = "futures-channel" 211 | version = "0.3.31" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 214 | dependencies = [ 215 | "futures-core", 216 | "futures-sink", 217 | ] 218 | 219 | [[package]] 220 | name = "futures-core" 221 | version = "0.3.31" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 224 | 225 | [[package]] 226 | name = "futures-executor" 227 | version = "0.3.31" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 230 | dependencies = [ 231 | "futures-core", 232 | "futures-task", 233 | "futures-util", 234 | ] 235 | 236 | [[package]] 237 | name = "futures-io" 238 | version = "0.3.31" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 241 | 242 | [[package]] 243 | name = "futures-macro" 244 | version = "0.3.31" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 247 | dependencies = [ 248 | "proc-macro2", 249 | "quote", 250 | "syn", 251 | ] 252 | 253 | [[package]] 254 | name = "futures-sink" 255 | version = "0.3.31" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 258 | 259 | [[package]] 260 | name = "futures-task" 261 | version = "0.3.31" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 264 | 265 | [[package]] 266 | name = "futures-util" 267 | version = "0.3.31" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 270 | dependencies = [ 271 | "futures-channel", 272 | "futures-core", 273 | "futures-io", 274 | "futures-macro", 275 | "futures-sink", 276 | "futures-task", 277 | "memchr", 278 | "pin-project-lite", 279 | "pin-utils", 280 | "slab", 281 | ] 282 | 283 | [[package]] 284 | name = "getrandom" 285 | version = "0.3.3" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 288 | dependencies = [ 289 | "cfg-if", 290 | "libc", 291 | "r-efi", 292 | "wasi 0.14.2+wasi-0.2.4", 293 | ] 294 | 295 | [[package]] 296 | name = "gimli" 297 | version = "0.31.1" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 300 | 301 | [[package]] 302 | name = "h2" 303 | version = "0.4.12" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 306 | dependencies = [ 307 | "atomic-waker", 308 | "bytes", 309 | "fnv", 310 | "futures-core", 311 | "futures-sink", 312 | "http", 313 | "indexmap", 314 | "slab", 315 | "tokio", 316 | "tokio-util", 317 | "tracing", 318 | ] 319 | 320 | [[package]] 321 | name = "hashbrown" 322 | version = "0.15.5" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 325 | 326 | [[package]] 327 | name = "heck" 328 | version = "0.5.0" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 331 | 332 | [[package]] 333 | name = "http" 334 | version = "1.3.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 337 | dependencies = [ 338 | "bytes", 339 | "fnv", 340 | "itoa", 341 | ] 342 | 343 | [[package]] 344 | name = "http-body" 345 | version = "1.0.1" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 348 | dependencies = [ 349 | "bytes", 350 | "http", 351 | ] 352 | 353 | [[package]] 354 | name = "http-body-util" 355 | version = "0.1.3" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 358 | dependencies = [ 359 | "bytes", 360 | "futures-core", 361 | "http", 362 | "http-body", 363 | "pin-project-lite", 364 | ] 365 | 366 | [[package]] 367 | name = "httparse" 368 | version = "1.10.1" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 371 | 372 | [[package]] 373 | name = "httpdate" 374 | version = "1.0.3" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 377 | 378 | [[package]] 379 | name = "hyper" 380 | version = "1.6.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 383 | dependencies = [ 384 | "bytes", 385 | "futures-channel", 386 | "futures-util", 387 | "h2", 388 | "http", 389 | "http-body", 390 | "httparse", 391 | "httpdate", 392 | "itoa", 393 | "pin-project-lite", 394 | "smallvec", 395 | "tokio", 396 | "want", 397 | ] 398 | 399 | [[package]] 400 | name = "hyper-timeout" 401 | version = "0.5.2" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" 404 | dependencies = [ 405 | "hyper", 406 | "hyper-util", 407 | "pin-project-lite", 408 | "tokio", 409 | "tower-service", 410 | ] 411 | 412 | [[package]] 413 | name = "hyper-util" 414 | version = "0.1.16" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" 417 | dependencies = [ 418 | "bytes", 419 | "futures-channel", 420 | "futures-core", 421 | "futures-util", 422 | "http", 423 | "http-body", 424 | "hyper", 425 | "libc", 426 | "pin-project-lite", 427 | "socket2", 428 | "tokio", 429 | "tower-service", 430 | "tracing", 431 | ] 432 | 433 | [[package]] 434 | name = "indexmap" 435 | version = "2.10.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 438 | dependencies = [ 439 | "equivalent", 440 | "hashbrown", 441 | ] 442 | 443 | [[package]] 444 | name = "integration_tests" 445 | version = "0.2.3" 446 | dependencies = [ 447 | "prost", 448 | "serial_test", 449 | "tokio", 450 | "tonic", 451 | "tonic-middleware", 452 | "tonic-prost", 453 | "tonic-prost-build", 454 | ] 455 | 456 | [[package]] 457 | name = "io-uring" 458 | version = "0.7.9" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" 461 | dependencies = [ 462 | "bitflags", 463 | "cfg-if", 464 | "libc", 465 | ] 466 | 467 | [[package]] 468 | name = "itertools" 469 | version = "0.14.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" 472 | dependencies = [ 473 | "either", 474 | ] 475 | 476 | [[package]] 477 | name = "itoa" 478 | version = "1.0.15" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 481 | 482 | [[package]] 483 | name = "libc" 484 | version = "0.2.174" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" 487 | 488 | [[package]] 489 | name = "linux-raw-sys" 490 | version = "0.9.4" 491 | source = "registry+https://github.com/rust-lang/crates.io-index" 492 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 493 | 494 | [[package]] 495 | name = "lock_api" 496 | version = "0.4.13" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 499 | dependencies = [ 500 | "autocfg", 501 | "scopeguard", 502 | ] 503 | 504 | [[package]] 505 | name = "log" 506 | version = "0.4.27" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 509 | 510 | [[package]] 511 | name = "matchit" 512 | version = "0.8.4" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 515 | 516 | [[package]] 517 | name = "memchr" 518 | version = "2.7.5" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 521 | 522 | [[package]] 523 | name = "mime" 524 | version = "0.3.17" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 527 | 528 | [[package]] 529 | name = "miniz_oxide" 530 | version = "0.8.9" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 533 | dependencies = [ 534 | "adler2", 535 | ] 536 | 537 | [[package]] 538 | name = "mio" 539 | version = "1.0.4" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 542 | dependencies = [ 543 | "libc", 544 | "wasi 0.11.1+wasi-snapshot-preview1", 545 | "windows-sys", 546 | ] 547 | 548 | [[package]] 549 | name = "multimap" 550 | version = "0.10.1" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" 553 | 554 | [[package]] 555 | name = "object" 556 | version = "0.36.7" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 559 | dependencies = [ 560 | "memchr", 561 | ] 562 | 563 | [[package]] 564 | name = "once_cell" 565 | version = "1.21.3" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 568 | 569 | [[package]] 570 | name = "parking_lot" 571 | version = "0.12.4" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 574 | dependencies = [ 575 | "lock_api", 576 | "parking_lot_core", 577 | ] 578 | 579 | [[package]] 580 | name = "parking_lot_core" 581 | version = "0.9.11" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 584 | dependencies = [ 585 | "cfg-if", 586 | "libc", 587 | "redox_syscall", 588 | "smallvec", 589 | "windows-targets", 590 | ] 591 | 592 | [[package]] 593 | name = "percent-encoding" 594 | version = "2.3.1" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 597 | 598 | [[package]] 599 | name = "petgraph" 600 | version = "0.7.1" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" 603 | dependencies = [ 604 | "fixedbitset", 605 | "indexmap", 606 | ] 607 | 608 | [[package]] 609 | name = "pin-project" 610 | version = "1.1.10" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 613 | dependencies = [ 614 | "pin-project-internal", 615 | ] 616 | 617 | [[package]] 618 | name = "pin-project-internal" 619 | version = "1.1.10" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 622 | dependencies = [ 623 | "proc-macro2", 624 | "quote", 625 | "syn", 626 | ] 627 | 628 | [[package]] 629 | name = "pin-project-lite" 630 | version = "0.2.16" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 633 | 634 | [[package]] 635 | name = "pin-utils" 636 | version = "0.1.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 639 | 640 | [[package]] 641 | name = "prettyplease" 642 | version = "0.2.36" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" 645 | dependencies = [ 646 | "proc-macro2", 647 | "syn", 648 | ] 649 | 650 | [[package]] 651 | name = "proc-macro2" 652 | version = "1.0.95" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 655 | dependencies = [ 656 | "unicode-ident", 657 | ] 658 | 659 | [[package]] 660 | name = "prost" 661 | version = "0.14.1" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" 664 | dependencies = [ 665 | "bytes", 666 | "prost-derive", 667 | ] 668 | 669 | [[package]] 670 | name = "prost-build" 671 | version = "0.14.1" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" 674 | dependencies = [ 675 | "heck", 676 | "itertools", 677 | "log", 678 | "multimap", 679 | "once_cell", 680 | "petgraph", 681 | "prettyplease", 682 | "prost", 683 | "prost-types", 684 | "pulldown-cmark", 685 | "pulldown-cmark-to-cmark", 686 | "regex", 687 | "syn", 688 | "tempfile", 689 | ] 690 | 691 | [[package]] 692 | name = "prost-derive" 693 | version = "0.14.1" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" 696 | dependencies = [ 697 | "anyhow", 698 | "itertools", 699 | "proc-macro2", 700 | "quote", 701 | "syn", 702 | ] 703 | 704 | [[package]] 705 | name = "prost-types" 706 | version = "0.14.1" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" 709 | dependencies = [ 710 | "prost", 711 | ] 712 | 713 | [[package]] 714 | name = "pulldown-cmark" 715 | version = "0.13.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" 718 | dependencies = [ 719 | "bitflags", 720 | "memchr", 721 | "unicase", 722 | ] 723 | 724 | [[package]] 725 | name = "pulldown-cmark-to-cmark" 726 | version = "21.0.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "e5b6a0769a491a08b31ea5c62494a8f144ee0987d86d670a8af4df1e1b7cde75" 729 | dependencies = [ 730 | "pulldown-cmark", 731 | ] 732 | 733 | [[package]] 734 | name = "quote" 735 | version = "1.0.40" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 738 | dependencies = [ 739 | "proc-macro2", 740 | ] 741 | 742 | [[package]] 743 | name = "r-efi" 744 | version = "5.3.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 747 | 748 | [[package]] 749 | name = "redox_syscall" 750 | version = "0.5.17" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" 753 | dependencies = [ 754 | "bitflags", 755 | ] 756 | 757 | [[package]] 758 | name = "regex" 759 | version = "1.11.1" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 762 | dependencies = [ 763 | "aho-corasick", 764 | "memchr", 765 | "regex-automata", 766 | "regex-syntax", 767 | ] 768 | 769 | [[package]] 770 | name = "regex-automata" 771 | version = "0.4.9" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 774 | dependencies = [ 775 | "aho-corasick", 776 | "memchr", 777 | "regex-syntax", 778 | ] 779 | 780 | [[package]] 781 | name = "regex-syntax" 782 | version = "0.8.5" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 785 | 786 | [[package]] 787 | name = "rustc-demangle" 788 | version = "0.1.26" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 791 | 792 | [[package]] 793 | name = "rustix" 794 | version = "1.0.8" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" 797 | dependencies = [ 798 | "bitflags", 799 | "errno", 800 | "libc", 801 | "linux-raw-sys", 802 | "windows-sys", 803 | ] 804 | 805 | [[package]] 806 | name = "rustversion" 807 | version = "1.0.21" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 810 | 811 | [[package]] 812 | name = "scc" 813 | version = "2.3.4" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4" 816 | dependencies = [ 817 | "sdd", 818 | ] 819 | 820 | [[package]] 821 | name = "scopeguard" 822 | version = "1.2.0" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 825 | 826 | [[package]] 827 | name = "sdd" 828 | version = "3.0.10" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" 831 | 832 | [[package]] 833 | name = "serde" 834 | version = "1.0.219" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 837 | dependencies = [ 838 | "serde_derive", 839 | ] 840 | 841 | [[package]] 842 | name = "serde_derive" 843 | version = "1.0.219" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 846 | dependencies = [ 847 | "proc-macro2", 848 | "quote", 849 | "syn", 850 | ] 851 | 852 | [[package]] 853 | name = "serial_test" 854 | version = "3.2.0" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" 857 | dependencies = [ 858 | "futures", 859 | "log", 860 | "once_cell", 861 | "parking_lot", 862 | "scc", 863 | "serial_test_derive", 864 | ] 865 | 866 | [[package]] 867 | name = "serial_test_derive" 868 | version = "3.2.0" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" 871 | dependencies = [ 872 | "proc-macro2", 873 | "quote", 874 | "syn", 875 | ] 876 | 877 | [[package]] 878 | name = "signal-hook-registry" 879 | version = "1.4.6" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" 882 | dependencies = [ 883 | "libc", 884 | ] 885 | 886 | [[package]] 887 | name = "slab" 888 | version = "0.4.11" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 891 | 892 | [[package]] 893 | name = "smallvec" 894 | version = "1.15.1" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 897 | 898 | [[package]] 899 | name = "socket2" 900 | version = "0.6.0" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" 903 | dependencies = [ 904 | "libc", 905 | "windows-sys", 906 | ] 907 | 908 | [[package]] 909 | name = "syn" 910 | version = "2.0.104" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 913 | dependencies = [ 914 | "proc-macro2", 915 | "quote", 916 | "unicode-ident", 917 | ] 918 | 919 | [[package]] 920 | name = "sync_wrapper" 921 | version = "1.0.2" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 924 | 925 | [[package]] 926 | name = "tempfile" 927 | version = "3.20.0" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" 930 | dependencies = [ 931 | "fastrand", 932 | "getrandom", 933 | "once_cell", 934 | "rustix", 935 | "windows-sys", 936 | ] 937 | 938 | [[package]] 939 | name = "tokio" 940 | version = "1.47.1" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" 943 | dependencies = [ 944 | "backtrace", 945 | "bytes", 946 | "io-uring", 947 | "libc", 948 | "mio", 949 | "parking_lot", 950 | "pin-project-lite", 951 | "signal-hook-registry", 952 | "slab", 953 | "socket2", 954 | "tokio-macros", 955 | "windows-sys", 956 | ] 957 | 958 | [[package]] 959 | name = "tokio-macros" 960 | version = "2.5.0" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 963 | dependencies = [ 964 | "proc-macro2", 965 | "quote", 966 | "syn", 967 | ] 968 | 969 | [[package]] 970 | name = "tokio-stream" 971 | version = "0.1.17" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 974 | dependencies = [ 975 | "futures-core", 976 | "pin-project-lite", 977 | "tokio", 978 | ] 979 | 980 | [[package]] 981 | name = "tokio-util" 982 | version = "0.7.16" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" 985 | dependencies = [ 986 | "bytes", 987 | "futures-core", 988 | "futures-sink", 989 | "pin-project-lite", 990 | "tokio", 991 | ] 992 | 993 | [[package]] 994 | name = "tonic" 995 | version = "0.14.1" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40" 998 | dependencies = [ 999 | "async-trait", 1000 | "axum", 1001 | "base64", 1002 | "bytes", 1003 | "h2", 1004 | "http", 1005 | "http-body", 1006 | "http-body-util", 1007 | "hyper", 1008 | "hyper-timeout", 1009 | "hyper-util", 1010 | "percent-encoding", 1011 | "pin-project", 1012 | "socket2", 1013 | "sync_wrapper", 1014 | "tokio", 1015 | "tokio-stream", 1016 | "tower", 1017 | "tower-layer", 1018 | "tower-service", 1019 | "tracing", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "tonic-build" 1024 | version = "0.14.1" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "49e323d8bba3be30833707e36d046deabf10a35ae8ad3cae576943ea8933e25d" 1027 | dependencies = [ 1028 | "prettyplease", 1029 | "proc-macro2", 1030 | "quote", 1031 | "syn", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "tonic-middleware" 1036 | version = "0.4.0" 1037 | dependencies = [ 1038 | "async-trait", 1039 | "futures-util", 1040 | "tonic", 1041 | "tower", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "tonic-prost" 1046 | version = "0.14.1" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "b9c511b9a96d40cb12b7d5d00464446acf3b9105fd3ce25437cfe41c92b1c87d" 1049 | dependencies = [ 1050 | "bytes", 1051 | "prost", 1052 | "tonic", 1053 | ] 1054 | 1055 | [[package]] 1056 | name = "tonic-prost-build" 1057 | version = "0.14.1" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "8ef298fcd01b15e135440c4b8c974460ceca4e6a5af7f1c933b08e4d2875efa1" 1060 | dependencies = [ 1061 | "prettyplease", 1062 | "proc-macro2", 1063 | "prost-build", 1064 | "prost-types", 1065 | "quote", 1066 | "syn", 1067 | "tempfile", 1068 | "tonic-build", 1069 | ] 1070 | 1071 | [[package]] 1072 | name = "tower" 1073 | version = "0.5.2" 1074 | source = "registry+https://github.com/rust-lang/crates.io-index" 1075 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1076 | dependencies = [ 1077 | "futures-core", 1078 | "futures-util", 1079 | "indexmap", 1080 | "pin-project-lite", 1081 | "slab", 1082 | "sync_wrapper", 1083 | "tokio", 1084 | "tokio-util", 1085 | "tower-layer", 1086 | "tower-service", 1087 | "tracing", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "tower-layer" 1092 | version = "0.3.3" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1095 | 1096 | [[package]] 1097 | name = "tower-service" 1098 | version = "0.3.3" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1101 | 1102 | [[package]] 1103 | name = "tracing" 1104 | version = "0.1.41" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1107 | dependencies = [ 1108 | "pin-project-lite", 1109 | "tracing-attributes", 1110 | "tracing-core", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "tracing-attributes" 1115 | version = "0.1.30" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 1118 | dependencies = [ 1119 | "proc-macro2", 1120 | "quote", 1121 | "syn", 1122 | ] 1123 | 1124 | [[package]] 1125 | name = "tracing-core" 1126 | version = "0.1.34" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 1129 | dependencies = [ 1130 | "once_cell", 1131 | ] 1132 | 1133 | [[package]] 1134 | name = "try-lock" 1135 | version = "0.2.5" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1138 | 1139 | [[package]] 1140 | name = "unicase" 1141 | version = "2.8.1" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 1144 | 1145 | [[package]] 1146 | name = "unicode-ident" 1147 | version = "1.0.18" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1150 | 1151 | [[package]] 1152 | name = "want" 1153 | version = "0.3.1" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1156 | dependencies = [ 1157 | "try-lock", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "wasi" 1162 | version = "0.11.1+wasi-snapshot-preview1" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 1165 | 1166 | [[package]] 1167 | name = "wasi" 1168 | version = "0.14.2+wasi-0.2.4" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 1171 | dependencies = [ 1172 | "wit-bindgen-rt", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "windows-sys" 1177 | version = "0.59.0" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1180 | dependencies = [ 1181 | "windows-targets", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "windows-targets" 1186 | version = "0.52.6" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1189 | dependencies = [ 1190 | "windows_aarch64_gnullvm", 1191 | "windows_aarch64_msvc", 1192 | "windows_i686_gnu", 1193 | "windows_i686_gnullvm", 1194 | "windows_i686_msvc", 1195 | "windows_x86_64_gnu", 1196 | "windows_x86_64_gnullvm", 1197 | "windows_x86_64_msvc", 1198 | ] 1199 | 1200 | [[package]] 1201 | name = "windows_aarch64_gnullvm" 1202 | version = "0.52.6" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1205 | 1206 | [[package]] 1207 | name = "windows_aarch64_msvc" 1208 | version = "0.52.6" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1211 | 1212 | [[package]] 1213 | name = "windows_i686_gnu" 1214 | version = "0.52.6" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1217 | 1218 | [[package]] 1219 | name = "windows_i686_gnullvm" 1220 | version = "0.52.6" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1223 | 1224 | [[package]] 1225 | name = "windows_i686_msvc" 1226 | version = "0.52.6" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1229 | 1230 | [[package]] 1231 | name = "windows_x86_64_gnu" 1232 | version = "0.52.6" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1235 | 1236 | [[package]] 1237 | name = "windows_x86_64_gnullvm" 1238 | version = "0.52.6" 1239 | source = "registry+https://github.com/rust-lang/crates.io-index" 1240 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1241 | 1242 | [[package]] 1243 | name = "windows_x86_64_msvc" 1244 | version = "0.52.6" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1247 | 1248 | [[package]] 1249 | name = "wit-bindgen-rt" 1250 | version = "0.39.0" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 1253 | dependencies = [ 1254 | "bitflags", 1255 | ] 1256 | --------------------------------------------------------------------------------