From 17e6fe57a0fcd78862e3b3bf1ea3bfaeb6551a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Mart=C3=ADnez?= <49537445+JasterV@users.noreply.github.com> Date: Tue, 27 Jan 2026 02:26:10 +0100 Subject: [PATCH] refactor: we can use tonic-reflection instead of having to generate the reflection client ourselfs (#29) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request removes the custom-generated gRPC reflection protocol code from the repository and switches to using the `tonic-reflection` crate’s built-in protocol definitions. It also cleans up related tooling and dependencies that are no longer needed. ### Migration to `tonic-reflection` for gRPC reflection * Replaced internal generated protocol types in `granc-core/src/reflection/client.rs` with imports from `tonic_reflection::pb::v1`, removing the need for the local `generated` module. [[1]](diffhunk://#diff-13deee04dd97de938cc46f0ef4faca083f3b471800e94cf45937122b83f01d57L19-L23) [[2]](diffhunk://#diff-13deee04dd97de938cc46f0ef4faca083f3b471800e94cf45937122b83f01d57R29-R33) * Deleted the `granc-core/src/reflection/generated.rs` module, which previously contained the generated Rust code for the reflection protocol. * Removed the reflection proto file (`granc-tools/proto/reflection.proto`) and the `granc-tools` crate, including its build tooling and dependencies, as they are no longer needed. [[1]](diffhunk://#diff-152ff715d002656dc972a294d86490c4857848392f54c73ac7e8818191ca617dL1-L149) [[2]](diffhunk://#diff-8a8fd674fd23e14d5c7a1ab242678a860560b0eee27cd248254510a3d585cbb4L1-L15) [[3]](diffhunk://#diff-2e9d962a08321605940b5a657135052fbcef87b5e360662bb527c96d9a615542L2-R2) [[4]](diffhunk://#diff-9375fd04332c86472d7be397ef09428cb86babd8826880a5835bd1d1c1bdbc08L43-L53) ### Dependency and configuration cleanup * Updated `granc-core/Cargo.toml` to add `tonic-reflection` as a regular dependency (not just a dev-dependency) and removed the now-unnecessary dev-dependency. ### Codebase simplification * Removed the now-unused `mod generated;` declaration from `granc-core/src/reflection.rs`. --- Cargo.lock | 7 - Cargo.toml | 2 +- Makefile.toml | 11 - granc-core/Cargo.toml | 2 +- granc-core/src/reflection.rs | 1 - granc-core/src/reflection/client.rs | 10 +- granc-core/src/reflection/generated.rs | 11 - .../generated/grpc.reflection.v1.rs | 272 ------------------ granc-tools/Cargo.toml | 15 - .../bin/generate_reflection_service.rs | 28 -- granc-tools/proto/reflection.proto | 149 ---------- 11 files changed, 7 insertions(+), 501 deletions(-) delete mode 100644 granc-core/src/reflection/generated.rs delete mode 100644 granc-core/src/reflection/generated/grpc.reflection.v1.rs delete mode 100644 granc-tools/Cargo.toml delete mode 100644 granc-tools/bin/generate_reflection_service.rs delete mode 100644 granc-tools/proto/reflection.proto diff --git a/Cargo.lock b/Cargo.lock index 4085962..4c293cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,13 +346,6 @@ dependencies = [ "tonic", ] -[[package]] -name = "granc-tools" -version = "0.0.0" -dependencies = [ - "tonic-prost-build", -] - [[package]] name = "granc_core" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 08f7fd7..c833535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["granc", "granc-core", "granc-tools", "echo-service"] +members = ["granc", "granc-core", "echo-service"] resolver = "2" [workspace.package] diff --git a/Makefile.toml b/Makefile.toml index 19927cc..d4c0afc 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -40,14 +40,3 @@ args = [ "-D", "warnings", ] - -[tasks.generate-reflection-service] -description = "Runs a binary that generates a reflection service client from the reflection proto definitions" -command = "cargo" -args = [ - "run", - "--bin", - "generate-reflection-service", - "--features", - "gen-proto", -] diff --git a/granc-core/Cargo.toml b/granc-core/Cargo.toml index 85ad3b0..bd0cd02 100644 --- a/granc-core/Cargo.toml +++ b/granc-core/Cargo.toml @@ -30,8 +30,8 @@ tokio = { workspace = true, features = ["sync"] } tokio-stream = "0.1.18" tonic = { workspace = true } tonic-prost = { workspace = true } +tonic-reflection = { workspace = true } [dev-dependencies] echo-service = { path = "../echo-service" } tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -tonic-reflection = { workspace = true } diff --git a/granc-core/src/reflection.rs b/granc-core/src/reflection.rs index ba351c0..301d49d 100644 --- a/granc-core/src/reflection.rs +++ b/granc-core/src/reflection.rs @@ -5,4 +5,3 @@ //! It enables the client to query a server for its own Protobuf schema at runtime, allowing //! `granc` to function without pre-compiled descriptors. pub mod client; -mod generated; diff --git a/granc-core/src/reflection/client.rs b/granc-core/src/reflection/client.rs index 600aa92..b52cb00 100644 --- a/granc-core/src/reflection/client.rs +++ b/granc-core/src/reflection/client.rs @@ -16,11 +16,6 @@ //! ## References //! //! * [gRPC Server Reflection Protocol](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md) -use super::generated::reflection_v1::{ - ServerReflectionRequest, ServerReflectionResponse, - server_reflection_client::ServerReflectionClient, server_reflection_request::MessageRequest, - server_reflection_response::MessageResponse, -}; use crate::BoxError; use futures_util::stream::once; use http_body::Body as HttpBody; @@ -31,6 +26,11 @@ use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; use tonic::transport::Channel; use tonic::{Streaming, client::GrpcService}; +use tonic_reflection::pb::v1::{ + ServerReflectionRequest, ServerReflectionResponse, + server_reflection_client::ServerReflectionClient, server_reflection_request::MessageRequest, + server_reflection_response::MessageResponse, +}; /// Errors that can occur during reflection resolution. #[derive(Debug, thiserror::Error)] diff --git a/granc-core/src/reflection/generated.rs b/granc-core/src/reflection/generated.rs deleted file mode 100644 index 3ba5992..0000000 --- a/granc-core/src/reflection/generated.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! # Generated Reflection Types -//! -//! This module contains the Rust code generated by `tonic-build` from the `reflection.proto` file. -//! It defines the structs and traits required to communicate with standard gRPC reflection servers. -//! -//! *Note: This is strictly for internal use by the `ReflectionClient`.* -//! -#[path = "./generated/grpc.reflection.v1.rs"] -#[allow(clippy::enum_variant_names)] -#[rustfmt::skip] -pub mod reflection_v1; diff --git a/granc-core/src/reflection/generated/grpc.reflection.v1.rs b/granc-core/src/reflection/generated/grpc.reflection.v1.rs deleted file mode 100644 index 9f4cf62..0000000 --- a/granc-core/src/reflection/generated/grpc.reflection.v1.rs +++ /dev/null @@ -1,272 +0,0 @@ -// This file is @generated by prost-build. -/// The message sent by the client when calling ServerReflectionInfo method. -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ServerReflectionRequest { - #[prost(string, tag = "1")] - pub host: ::prost::alloc::string::String, - /// To use reflection service, the client should set one of the following - /// fields in message_request. The server distinguishes requests by their - /// defined field and then handles them using corresponding methods. - #[prost(oneof = "server_reflection_request::MessageRequest", tags = "3, 4, 5, 6, 7")] - pub message_request: ::core::option::Option< - server_reflection_request::MessageRequest, - >, -} -/// Nested message and enum types in `ServerReflectionRequest`. -pub mod server_reflection_request { - /// To use reflection service, the client should set one of the following - /// fields in message_request. The server distinguishes requests by their - /// defined field and then handles them using corresponding methods. - #[derive(Clone, PartialEq, Eq, Hash, ::prost::Oneof)] - pub enum MessageRequest { - /// Find a proto file by the file name. - #[prost(string, tag = "3")] - FileByFilename(::prost::alloc::string::String), - /// Find the proto file that declares the given fully-qualified symbol name. - /// This field should be a fully-qualified symbol name - /// (e.g. .\[.\] or .). - #[prost(string, tag = "4")] - FileContainingSymbol(::prost::alloc::string::String), - /// Find the proto file which defines an extension extending the given - /// message type with the given field number. - #[prost(message, tag = "5")] - FileContainingExtension(super::ExtensionRequest), - /// Finds the tag numbers used by all known extensions of the given message - /// type, and appends them to ExtensionNumberResponse in an undefined order. - /// Its corresponding method is best-effort: it's not guaranteed that the - /// reflection service will implement this method, and it's not guaranteed - /// that this method will provide all extensions. Returns - /// StatusCode::UNIMPLEMENTED if it's not implemented. - /// This field should be a fully-qualified type name. The format is - /// . - #[prost(string, tag = "6")] - AllExtensionNumbersOfType(::prost::alloc::string::String), - /// List the full names of registered services. The content will not be - /// checked. - #[prost(string, tag = "7")] - ListServices(::prost::alloc::string::String), - } -} -/// The type name and extension number sent by the client when requesting -/// file_containing_extension. -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ExtensionRequest { - /// Fully-qualified type name. The format should be . - #[prost(string, tag = "1")] - pub containing_type: ::prost::alloc::string::String, - #[prost(int32, tag = "2")] - pub extension_number: i32, -} -/// The message sent by the server to answer ServerReflectionInfo method. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ServerReflectionResponse { - #[prost(string, tag = "1")] - pub valid_host: ::prost::alloc::string::String, - #[prost(message, optional, tag = "2")] - pub original_request: ::core::option::Option, - /// The server sets one of the following fields according to the message_request - /// in the request. - #[prost(oneof = "server_reflection_response::MessageResponse", tags = "4, 5, 6, 7")] - pub message_response: ::core::option::Option< - server_reflection_response::MessageResponse, - >, -} -/// Nested message and enum types in `ServerReflectionResponse`. -pub mod server_reflection_response { - /// The server sets one of the following fields according to the message_request - /// in the request. - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum MessageResponse { - /// This message is used to answer file_by_filename, file_containing_symbol, - /// file_containing_extension requests with transitive dependencies. - /// As the repeated label is not allowed in oneof fields, we use a - /// FileDescriptorResponse message to encapsulate the repeated fields. - /// The reflection service is allowed to avoid sending FileDescriptorProtos - /// that were previously sent in response to earlier requests in the stream. - #[prost(message, tag = "4")] - FileDescriptorResponse(super::FileDescriptorResponse), - /// This message is used to answer all_extension_numbers_of_type requests. - #[prost(message, tag = "5")] - AllExtensionNumbersResponse(super::ExtensionNumberResponse), - /// This message is used to answer list_services requests. - #[prost(message, tag = "6")] - ListServicesResponse(super::ListServiceResponse), - /// This message is used when an error occurs. - #[prost(message, tag = "7")] - ErrorResponse(super::ErrorResponse), - } -} -/// Serialized FileDescriptorProto messages sent by the server answering -/// a file_by_filename, file_containing_symbol, or file_containing_extension -/// request. -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct FileDescriptorResponse { - /// Serialized FileDescriptorProto messages. We avoid taking a dependency on - /// descriptor.proto, which uses proto2 only features, by making them opaque - /// bytes instead. - #[prost(bytes = "vec", repeated, tag = "1")] - pub file_descriptor_proto: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, -} -/// A list of extension numbers sent by the server answering -/// all_extension_numbers_of_type request. -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ExtensionNumberResponse { - /// Full name of the base type, including the package name. The format - /// is . - #[prost(string, tag = "1")] - pub base_type_name: ::prost::alloc::string::String, - #[prost(int32, repeated, tag = "2")] - pub extension_number: ::prost::alloc::vec::Vec, -} -/// A list of ServiceResponse sent by the server answering list_services request. -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ListServiceResponse { - /// The information of each service may be expanded in the future, so we use - /// ServiceResponse message to encapsulate it. - #[prost(message, repeated, tag = "1")] - pub service: ::prost::alloc::vec::Vec, -} -/// The information of a single service used by ListServiceResponse to answer -/// list_services request. -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ServiceResponse { - /// Full name of a registered service, including its package name. The format - /// is . - #[prost(string, tag = "1")] - pub name: ::prost::alloc::string::String, -} -/// The error code and error message sent by the server when an error occurs. -#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] -pub struct ErrorResponse { - /// This field uses the error codes defined in grpc::StatusCode. - #[prost(int32, tag = "1")] - pub error_code: i32, - #[prost(string, tag = "2")] - pub error_message: ::prost::alloc::string::String, -} -/// Generated client implementations. -pub mod server_reflection_client { - #![allow( - unused_variables, - dead_code, - missing_docs, - clippy::wildcard_imports, - clippy::let_unit_value, - )] - use tonic::codegen::*; - use tonic::codegen::http::Uri; - #[derive(Debug, Clone)] - pub struct ServerReflectionClient { - inner: tonic::client::Grpc, - } - impl ServerReflectionClient { - /// Attempt to create a new client by connecting to a given endpoint. - pub async fn connect(dst: D) -> Result - where - D: TryInto, - D::Error: Into, - { - let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; - Ok(Self::new(conn)) - } - } - impl ServerReflectionClient - where - T: tonic::client::GrpcService, - T::Error: Into, - T::ResponseBody: Body + std::marker::Send + 'static, - ::Error: Into + std::marker::Send, - { - pub fn new(inner: T) -> Self { - let inner = tonic::client::Grpc::new(inner); - Self { inner } - } - pub fn with_origin(inner: T, origin: Uri) -> Self { - let inner = tonic::client::Grpc::with_origin(inner, origin); - Self { inner } - } - pub fn with_interceptor( - inner: T, - interceptor: F, - ) -> ServerReflectionClient> - where - F: tonic::service::Interceptor, - T::ResponseBody: Default, - T: tonic::codegen::Service< - http::Request, - Response = http::Response< - >::ResponseBody, - >, - >, - , - >>::Error: Into + std::marker::Send + std::marker::Sync, - { - ServerReflectionClient::new(InterceptedService::new(inner, interceptor)) - } - /// Compress requests with the given encoding. - /// - /// This requires the server to support it otherwise it might respond with an - /// error. - #[must_use] - pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.inner = self.inner.send_compressed(encoding); - self - } - /// Enable decompressing responses. - #[must_use] - pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { - self.inner = self.inner.accept_compressed(encoding); - self - } - /// Limits the maximum size of a decoded message. - /// - /// Default: `4MB` - #[must_use] - pub fn max_decoding_message_size(mut self, limit: usize) -> Self { - self.inner = self.inner.max_decoding_message_size(limit); - self - } - /// Limits the maximum size of an encoded message. - /// - /// Default: `usize::MAX` - #[must_use] - pub fn max_encoding_message_size(mut self, limit: usize) -> Self { - self.inner = self.inner.max_encoding_message_size(limit); - self - } - /// The reflection service is structured as a bidirectional stream, ensuring - /// all related requests go to a single server. - pub async fn server_reflection_info( - &mut self, - request: impl tonic::IntoStreamingRequest< - Message = super::ServerReflectionRequest, - >, - ) -> std::result::Result< - tonic::Response>, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; - let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/grpc.reflection.v1.ServerReflection/ServerReflectionInfo", - ); - let mut req = request.into_streaming_request(); - req.extensions_mut() - .insert( - GrpcMethod::new( - "grpc.reflection.v1.ServerReflection", - "ServerReflectionInfo", - ), - ); - self.inner.streaming(req, path, codec).await - } - } -} diff --git a/granc-tools/Cargo.toml b/granc-tools/Cargo.toml deleted file mode 100644 index 3ee8535..0000000 --- a/granc-tools/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "granc-tools" -edition = { workspace = true } -publish = false - -[features] -gen-proto = ["dep:tonic-prost-build"] - -[[bin]] -name = "generate-reflection-service" -path = "bin/generate_reflection_service.rs" -required-features = ["gen-proto"] - -[dependencies] -tonic-prost-build = { workspace = true, optional = true } diff --git a/granc-tools/bin/generate_reflection_service.rs b/granc-tools/bin/generate_reflection_service.rs deleted file mode 100644 index 30c9fcd..0000000 --- a/granc-tools/bin/generate_reflection_service.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::env; -use std::fs; -use std::path::PathBuf; - -fn main() -> Result<(), Box> { - println!("Generating Reflection Service types..."); - - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let out_dir = manifest_dir.join("../granc-core/src/reflection/generated"); - - let proto_file = manifest_dir.join("proto/reflection.proto"); - let proto_folder = manifest_dir.join("proto"); - - if !out_dir.exists() { - fs::create_dir_all(&out_dir)?; - } - - tonic_prost_build::configure() - .build_server(false) - .build_client(true) - .out_dir(&out_dir) - .compile_protos(&[proto_file], &[proto_folder]) - .unwrap(); - - println!("Done! Generated files are in src/reflection/generated"); - - Ok(()) -} diff --git a/granc-tools/proto/reflection.proto b/granc-tools/proto/reflection.proto deleted file mode 100644 index 4176ec5..0000000 --- a/granc-tools/proto/reflection.proto +++ /dev/null @@ -1,149 +0,0 @@ -// This file has been copied from the original gRPC Authors. -// -// The Apache License, Version 2.0 is used in this project and applies -// to this file too. -// -// I explicitly state that I haven't written this file and the copyright -// belongs completely to the original authors. -// -// Copyright 2016 The gRPC Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Service exported by server reflection. A more complete description of how -// server reflection works can be found at -// https://github.com/grpc/grpc/blob/master/doc/server-reflection.md -// -// The canonical version of this proto can be found at -// https://github.com/grpc/grpc-proto/blob/master/grpc/reflection/v1/reflection.proto - -syntax = "proto3"; - -package grpc.reflection.v1; - -service ServerReflection { - // The reflection service is structured as a bidirectional stream, ensuring - // all related requests go to a single server. - rpc ServerReflectionInfo(stream ServerReflectionRequest) - returns (stream ServerReflectionResponse); -} - -// The message sent by the client when calling ServerReflectionInfo method. -message ServerReflectionRequest { - string host = 1; - // To use reflection service, the client should set one of the following - // fields in message_request. The server distinguishes requests by their - // defined field and then handles them using corresponding methods. - oneof message_request { - // Find a proto file by the file name. - string file_by_filename = 3; - - // Find the proto file that declares the given fully-qualified symbol name. - // This field should be a fully-qualified symbol name - // (e.g. .[.] or .). - string file_containing_symbol = 4; - - // Find the proto file which defines an extension extending the given - // message type with the given field number. - ExtensionRequest file_containing_extension = 5; - - // Finds the tag numbers used by all known extensions of the given message - // type, and appends them to ExtensionNumberResponse in an undefined order. - // Its corresponding method is best-effort: it's not guaranteed that the - // reflection service will implement this method, and it's not guaranteed - // that this method will provide all extensions. Returns - // StatusCode::UNIMPLEMENTED if it's not implemented. - // This field should be a fully-qualified type name. The format is - // . - string all_extension_numbers_of_type = 6; - - // List the full names of registered services. The content will not be - // checked. - string list_services = 7; - } -} - -// The type name and extension number sent by the client when requesting -// file_containing_extension. -message ExtensionRequest { - // Fully-qualified type name. The format should be . - string containing_type = 1; - int32 extension_number = 2; -} - -// The message sent by the server to answer ServerReflectionInfo method. -message ServerReflectionResponse { - string valid_host = 1; - ServerReflectionRequest original_request = 2; - // The server sets one of the following fields according to the message_request - // in the request. - oneof message_response { - // This message is used to answer file_by_filename, file_containing_symbol, - // file_containing_extension requests with transitive dependencies. - // As the repeated label is not allowed in oneof fields, we use a - // FileDescriptorResponse message to encapsulate the repeated fields. - // The reflection service is allowed to avoid sending FileDescriptorProtos - // that were previously sent in response to earlier requests in the stream. - FileDescriptorResponse file_descriptor_response = 4; - - // This message is used to answer all_extension_numbers_of_type requests. - ExtensionNumberResponse all_extension_numbers_response = 5; - - // This message is used to answer list_services requests. - ListServiceResponse list_services_response = 6; - - // This message is used when an error occurs. - ErrorResponse error_response = 7; - } -} - -// Serialized FileDescriptorProto messages sent by the server answering -// a file_by_filename, file_containing_symbol, or file_containing_extension -// request. -message FileDescriptorResponse { - // Serialized FileDescriptorProto messages. We avoid taking a dependency on - // descriptor.proto, which uses proto2 only features, by making them opaque - // bytes instead. - repeated bytes file_descriptor_proto = 1; -} - -// A list of extension numbers sent by the server answering -// all_extension_numbers_of_type request. -message ExtensionNumberResponse { - // Full name of the base type, including the package name. The format - // is . - string base_type_name = 1; - repeated int32 extension_number = 2; -} - -// A list of ServiceResponse sent by the server answering list_services request. -message ListServiceResponse { - // The information of each service may be expanded in the future, so we use - // ServiceResponse message to encapsulate it. - repeated ServiceResponse service = 1; -} - -// The information of a single service used by ListServiceResponse to answer -// list_services request. -message ServiceResponse { - // Full name of a registered service, including its package name. The format - // is . - string name = 1; -} - -// The error code and error message sent by the server when an error occurs. -message ErrorResponse { - // This field uses the error codes defined in grpc::StatusCode. - int32 error_code = 1; - string error_message = 2; -}