refactor: make state implementations cleaner

This commit is contained in:
JasterV 2026-01-28 12:40:04 +01:00
commit 5fc3a68ba1
5 changed files with 116 additions and 104 deletions

View file

@ -35,9 +35,12 @@
pub mod offline;
pub mod online;
pub mod online_without_reflection;
mod types;
pub use types::*;
use crate::{grpc::client::GrpcClient, reflection::client::ReflectionClient};
use prost_reflect::{DescriptorPool, EnumDescriptor, MessageDescriptor, ServiceDescriptor};
use prost_reflect::DescriptorPool;
use std::fmt::Debug;
use tonic::transport::Channel;
@ -49,6 +52,12 @@ pub struct GrancClient<T> {
state: T,
}
impl<T> GrancClient<T> {
pub(crate) fn new(state: T) -> Self {
Self { state }
}
}
/// State: Connected to server, Schema from Server Reflection.
#[derive(Debug, Clone)]
pub struct Online<S = Channel> {
@ -63,69 +72,36 @@ pub struct OnlineWithoutReflection<S = Channel> {
pool: DescriptorPool,
}
impl<S> OnlineWithoutReflection<S> {
pub(crate) fn new(grpc_client: GrpcClient<S>, pool: DescriptorPool) -> Self {
Self { pool, grpc_client }
}
}
/// State: Disconnected, Schema from local FileDescriptor.
#[derive(Debug, Clone)]
pub struct Offline {
pool: DescriptorPool,
}
/// A request object encapsulating all necessary information to perform a dynamic gRPC call.
#[derive(Debug, Clone)]
pub struct DynamicRequest {
/// The JSON body of the request.
/// - For Unary/ServerStreaming: An Object `{}`.
/// - For ClientStreaming/Bidirectional: An Array of Objects `[{}]`.
pub body: serde_json::Value,
/// Custom gRPC metadata (headers) to attach to the request.
pub headers: Vec<(String, String)>,
/// The fully qualified name of the service (e.g., `my.package.Service`).
pub service: String,
/// The name of the method to call (e.g., `SayHello`).
pub method: String,
}
/// The result of a dynamic gRPC call.
#[derive(Debug, Clone)]
pub enum DynamicResponse {
/// A single response message (for Unary and Client Streaming calls).
Unary(Result<serde_json::Value, tonic::Status>),
/// A stream of response messages (for Server Streaming and Bidirectional calls).
Streaming(Result<Vec<Result<serde_json::Value, tonic::Status>>, tonic::Status>),
}
/// A generic wrapper for different types of Protobuf descriptors.
///
/// This enum allows the client to return a single type when resolving symbols,
/// regardless of whether the symbol points to a Service, a Message, or an Enum.
#[derive(Debug, Clone)]
pub enum Descriptor {
MessageDescriptor(MessageDescriptor),
ServiceDescriptor(ServiceDescriptor),
EnumDescriptor(EnumDescriptor),
}
impl Descriptor {
/// Returns the inner [`MessageDescriptor`] if this variant is `MessageDescriptor`.
pub fn message_descriptor(&self) -> Option<&MessageDescriptor> {
match self {
Descriptor::MessageDescriptor(d) => Some(d),
_ => None,
impl Offline {
pub(crate) fn new(pool: DescriptorPool) -> Self {
Self { pool }
}
}
/// Returns the inner [`ServiceDescriptor`] if this variant is `ServiceDescriptor`.
pub fn service_descriptor(&self) -> Option<&ServiceDescriptor> {
match self {
Descriptor::ServiceDescriptor(d) => Some(d),
_ => None,
pub trait OfflineReflectionState {
fn descriptor_pool(&self) -> &DescriptorPool;
}
impl OfflineReflectionState for Offline {
fn descriptor_pool(&self) -> &DescriptorPool {
&self.pool
}
}
/// Returns the inner [`EnumDescriptor`] if this variant is `EnumDescriptor`.
pub fn enum_descriptor(&self) -> Option<&EnumDescriptor> {
match self {
Descriptor::EnumDescriptor(d) => Some(d),
_ => None,
}
impl<S> OfflineReflectionState for OnlineWithoutReflection<S> {
fn descriptor_pool(&self) -> &DescriptorPool {
&self.pool
}
}

View file

@ -4,7 +4,8 @@
//! `DescriptorPool` but is **not connected** to any gRPC server.
//!
//! In this state, the client is strictly limited to introspection tasks.
use super::{Descriptor, GrancClient, Offline};
use super::{GrancClient, Offline};
use crate::client::{OfflineReflectionState, types::Descriptor};
use prost_reflect::{DescriptorError, DescriptorPool};
impl GrancClient<Offline> {
@ -28,7 +29,12 @@ impl GrancClient<Offline> {
state: Offline { pool },
})
}
}
impl<T> GrancClient<T>
where
T: OfflineReflectionState,
{
/// Lists all services defined in the local `DescriptorPool`.
///
/// # Returns
@ -36,7 +42,7 @@ impl GrancClient<Offline> {
/// A list of fully qualified service names (e.g. `helloworld.Greeter`).
pub fn list_services(&self) -> Vec<String> {
self.state
.pool
.descriptor_pool()
.services()
.map(|s| s.full_name().to_string())
.collect()
@ -53,7 +59,7 @@ impl GrancClient<Offline> {
/// * `Some(Descriptor)` - The resolved descriptor if found.
/// * `None` - If the symbol does not exist in the pool.
pub fn get_descriptor_by_symbol(&self, symbol: &str) -> Option<Descriptor> {
let pool = &self.state.pool;
let pool = self.state.descriptor_pool();
if let Some(descriptor) = pool.get_service_by_name(symbol) {
return Some(Descriptor::ServiceDescriptor(descriptor));

View file

@ -7,6 +7,7 @@ use super::{
};
use crate::{
BoxError,
client::Offline,
grpc::client::GrpcClient,
reflection::client::{ReflectionClient, ReflectionResolveError},
};
@ -103,7 +104,11 @@ where
file_descriptor: Vec<u8>,
) -> Result<GrancClient<OnlineWithoutReflection<S>>, DescriptorError> {
let pool = DescriptorPool::decode(file_descriptor.as_slice())?;
Ok(GrancClient::new(self.state.grpc_client, pool))
Ok(GrancClient::new(OnlineWithoutReflection::new(
self.state.grpc_client,
pool,
)))
}
/// Lists services available on the server via Reflection.
@ -134,9 +139,7 @@ where
let pool = DescriptorPool::from_file_descriptor_set(fd_set)?;
// Build a temporary OnlineWithoutReflection client just to reuse lookup logic
let mut client =
GrancClient::<OnlineWithoutReflection<S>>::new(self.state.grpc_client.clone(), pool);
let client = GrancClient::new(Offline::new(pool));
client
.get_descriptor_by_symbol(symbol)
@ -159,8 +162,11 @@ where
.await?;
let pool = DescriptorPool::from_file_descriptor_set(fd_set)?;
let mut client =
GrancClient::<OnlineWithoutReflection<S>>::new(self.state.grpc_client.clone(), pool);
let mut client = GrancClient::new(OnlineWithoutReflection::new(
self.state.grpc_client.clone(),
pool,
));
Ok(client.dynamic(request).await?)
}
}

View file

@ -2,14 +2,10 @@
//!
//! This module defines the `GrancClient` behavior when it is connected to a server
//! but uses a local, in-memory `DescriptorPool` (Static schema) to resolve messages.
use super::{Descriptor, DynamicRequest, DynamicResponse, GrancClient, OnlineWithoutReflection};
use crate::{
BoxError,
grpc::client::{GrpcClient, GrpcRequestError},
};
use super::{DynamicRequest, DynamicResponse, GrancClient, OnlineWithoutReflection};
use crate::{BoxError, client::OfflineReflectionState, grpc::client::GrpcRequestError};
use futures_util::{Stream, StreamExt};
use http_body::Body as HttpBody;
use prost_reflect::DescriptorPool;
use std::fmt::Debug;
/// Errors that can occur during a dynamic call in OnlineWithoutReflection mode.
@ -25,47 +21,12 @@ pub enum DynamicCallError {
GrpcRequestError(#[from] GrpcRequestError),
}
impl<S> GrancClient<OnlineWithoutReflection<S>>
where
S: Clone,
{
pub(crate) fn new(grpc_client: GrpcClient<S>, pool: DescriptorPool) -> Self {
Self {
state: OnlineWithoutReflection { grpc_client, pool },
}
}
}
impl<S> GrancClient<OnlineWithoutReflection<S>>
where
S: tonic::client::GrpcService<tonic::body::Body> + Clone,
S::ResponseBody: HttpBody<Data = tonic::codegen::Bytes> + Send + 'static,
<S::ResponseBody as HttpBody>::Error: Into<BoxError> + Send,
{
/// Lists all services in the local descriptor pool.
pub fn list_services(&mut self) -> Vec<String> {
self.state
.pool
.services()
.map(|s| s.full_name().to_string())
.collect()
}
/// Looks up a symbol in the local descriptor pool.
pub fn get_descriptor_by_symbol(&mut self, symbol: &str) -> Option<Descriptor> {
let pool = &self.state.pool;
if let Some(descriptor) = pool.get_service_by_name(symbol) {
return Some(Descriptor::ServiceDescriptor(descriptor));
}
if let Some(descriptor) = pool.get_message_by_name(symbol) {
return Some(Descriptor::MessageDescriptor(descriptor));
}
if let Some(descriptor) = pool.get_enum_by_name(symbol) {
return Some(Descriptor::EnumDescriptor(descriptor));
}
None
}
/// Executes a dynamic gRPC request using the local descriptor pool.
pub async fn dynamic(
&mut self,
@ -73,7 +34,7 @@ where
) -> Result<DynamicResponse, DynamicCallError> {
let method = self
.state
.pool
.descriptor_pool()
.get_service_by_name(&request.service)
.ok_or_else(|| DynamicCallError::ServiceNotFound(request.service.clone()))?
.methods()

View file

@ -0,0 +1,63 @@
use prost_reflect::{EnumDescriptor, MessageDescriptor, ServiceDescriptor};
use std::fmt::Debug;
/// A request object encapsulating all necessary information to perform a dynamic gRPC call.
#[derive(Debug, Clone)]
pub struct DynamicRequest {
/// The JSON body of the request.
/// - For Unary/ServerStreaming: An Object `{}`.
/// - For ClientStreaming/Bidirectional: An Array of Objects `[{}]`.
pub body: serde_json::Value,
/// Custom gRPC metadata (headers) to attach to the request.
pub headers: Vec<(String, String)>,
/// The fully qualified name of the service (e.g., `my.package.Service`).
pub service: String,
/// The name of the method to call (e.g., `SayHello`).
pub method: String,
}
/// The result of a dynamic gRPC call.
#[derive(Debug, Clone)]
pub enum DynamicResponse {
/// A single response message (for Unary and Client Streaming calls).
Unary(Result<serde_json::Value, tonic::Status>),
/// A stream of response messages (for Server Streaming and Bidirectional calls).
Streaming(Result<Vec<Result<serde_json::Value, tonic::Status>>, tonic::Status>),
}
/// A generic wrapper for different types of Protobuf descriptors.
///
/// This enum allows the client to return a single type when resolving symbols,
/// regardless of whether the symbol points to a Service, a Message, or an Enum.
#[derive(Debug, Clone)]
pub enum Descriptor {
MessageDescriptor(MessageDescriptor),
ServiceDescriptor(ServiceDescriptor),
EnumDescriptor(EnumDescriptor),
}
impl Descriptor {
/// Returns the inner [`MessageDescriptor`] if this variant is `MessageDescriptor`.
pub fn message_descriptor(&self) -> Option<&MessageDescriptor> {
match self {
Descriptor::MessageDescriptor(d) => Some(d),
_ => None,
}
}
/// Returns the inner [`ServiceDescriptor`] if this variant is `ServiceDescriptor`.
pub fn service_descriptor(&self) -> Option<&ServiceDescriptor> {
match self {
Descriptor::ServiceDescriptor(d) => Some(d),
_ => None,
}
}
/// Returns the inner [`EnumDescriptor`] if this variant is `EnumDescriptor`.
pub fn enum_descriptor(&self) -> Option<&EnumDescriptor> {
match self {
Descriptor::EnumDescriptor(d) => Some(d),
_ => None,
}
}
}