forked from hyperium/tonic
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tonic-reflection: expose both v1alpha and v1 reflection server builders
While testing the new tonic 0.12 release, we discovered that hyperium#1701 upgraded the reflection protocol to `v1` from `v1alpha`. This breaks compatibility with popular gRPC debugging tools like `Postman` and `Kreya`, who only use the `v1alpha` protocol. Changes: * Expose V1Builder and V1AlphaBuilder for the different protocols * Add a type alias Builder for backwards compatibility * Move the descriptor parser outside the builder * Add basic tests for the different protocol versions
- Loading branch information
Showing
5 changed files
with
671 additions
and
192 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
pub use crate::pb::v1::server_reflection_server as server_reflection_server_v1; | ||
pub use crate::pb::v1alpha::server_reflection_server as server_reflection_server_v1alpha; | ||
|
||
pub use crate::server::v1::V1Builder; | ||
pub use crate::server::v1alpha::V1AlphaBuilder; | ||
|
||
use prost::DecodeError; | ||
use std::fmt::{Display, Formatter}; | ||
|
||
mod parser; | ||
mod v1; | ||
mod v1alpha; | ||
|
||
/// Represents an error in the construction of a gRPC Reflection Service. | ||
#[derive(Debug)] | ||
pub enum Error { | ||
/// An error was encountered decoding a `prost_types::FileDescriptorSet` from a buffer. | ||
DecodeError(prost::DecodeError), | ||
/// An invalid `prost_types::FileDescriptorProto` was encountered. | ||
InvalidFileDescriptorSet(String), | ||
} | ||
|
||
impl From<DecodeError> for Error { | ||
fn from(e: DecodeError) -> Self { | ||
Error::DecodeError(e) | ||
} | ||
} | ||
|
||
impl std::error::Error for Error {} | ||
|
||
impl Display for Error { | ||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
Error::DecodeError(_) => f.write_str("error decoding FileDescriptorSet from buffer"), | ||
Error::InvalidFileDescriptorSet(s) => { | ||
write!(f, "invalid FileDescriptorSet - {}", s) | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// A builder used to construct a gRPC Reflection Service (backwards compatibility). | ||
pub type Builder<'b> = V1Builder<'b>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
use crate::server::Error; | ||
use prost::Message; | ||
use prost_types::{ | ||
DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, FileDescriptorProto, | ||
FileDescriptorSet, | ||
}; | ||
use std::collections::HashMap; | ||
use std::sync::Arc; | ||
|
||
#[derive(Default)] | ||
pub(crate) struct ParserInfo { | ||
pub(crate) service_names: Vec<String>, | ||
pub(crate) symbols: HashMap<String, Arc<FileDescriptorProto>>, | ||
pub(crate) files: HashMap<String, Arc<FileDescriptorProto>>, | ||
} | ||
|
||
pub(crate) fn process( | ||
encoded_file_descriptor_sets: Vec<&[u8]>, | ||
file_descriptor_sets: Vec<FileDescriptorSet>, | ||
) -> Result<ParserInfo, Error> { | ||
let mut all_fds = file_descriptor_sets.clone(); | ||
|
||
for encoded in &encoded_file_descriptor_sets { | ||
let decoded = FileDescriptorSet::decode(*encoded)?; | ||
all_fds.push(decoded); | ||
} | ||
|
||
let mut info = ParserInfo::default(); | ||
|
||
for fds in all_fds { | ||
for fd in fds.file { | ||
let name = match fd.name.clone() { | ||
None => { | ||
return Err(Error::InvalidFileDescriptorSet("missing name".to_string())); | ||
} | ||
Some(n) => n, | ||
}; | ||
|
||
if info.files.contains_key(&name) { | ||
continue; | ||
} | ||
|
||
let fd = Arc::new(fd); | ||
info.files.insert(name, fd.clone()); | ||
|
||
let result = process_file(fd)?; | ||
|
||
info.service_names.extend(result.service_names); | ||
info.symbols.extend(result.symbols.into_iter()); | ||
} | ||
} | ||
|
||
Ok(info) | ||
} | ||
|
||
type SymbolArray = Vec<(String, Arc<FileDescriptorProto>)>; | ||
|
||
struct File { | ||
service_names: Vec<String>, | ||
symbols: SymbolArray, | ||
} | ||
|
||
fn process_file(fd: Arc<FileDescriptorProto>) -> Result<File, Error> { | ||
let prefix = &fd.package.clone().unwrap_or_default(); | ||
let mut service_names = vec![]; | ||
let mut symbols = vec![]; | ||
|
||
for msg in &fd.message_type { | ||
symbols.extend(process_message(fd.clone(), prefix, msg)?); | ||
} | ||
|
||
for en in &fd.enum_type { | ||
symbols.extend(process_enum(fd.clone(), prefix, en)?); | ||
} | ||
|
||
for service in &fd.service { | ||
let service_name = extract_name(prefix, "service", service.name.as_ref())?; | ||
service_names.push(service_name.clone()); | ||
symbols.extend(vec![(service_name.clone(), fd.clone())]); | ||
|
||
for method in &service.method { | ||
let method_name = extract_name(&service_name, "method", method.name.as_ref())?; | ||
symbols.extend(vec![(method_name, fd.clone())]); | ||
} | ||
} | ||
|
||
Ok(File { | ||
service_names, | ||
symbols, | ||
}) | ||
} | ||
|
||
fn process_message( | ||
fd: Arc<FileDescriptorProto>, | ||
prefix: &str, | ||
msg: &DescriptorProto, | ||
) -> Result<SymbolArray, Error> { | ||
let message_name = extract_name(prefix, "message", msg.name.as_ref())?; | ||
let mut symbols = vec![(message_name.clone(), fd.clone())]; | ||
|
||
for nested in &msg.nested_type { | ||
symbols.extend(process_message(fd.clone(), &message_name, nested)?); | ||
} | ||
|
||
for en in &msg.enum_type { | ||
symbols.extend(process_enum(fd.clone(), &message_name, en)?); | ||
} | ||
|
||
for field in &msg.field { | ||
symbols.extend(process_field(fd.clone(), &message_name, field)?); | ||
} | ||
|
||
for oneof in &msg.oneof_decl { | ||
let oneof_name = extract_name(&message_name, "oneof", oneof.name.as_ref())?; | ||
symbols.extend(vec![(oneof_name, fd.clone())]); | ||
} | ||
|
||
Ok(symbols) | ||
} | ||
|
||
fn process_enum( | ||
fd: Arc<FileDescriptorProto>, | ||
prefix: &str, | ||
en: &EnumDescriptorProto, | ||
) -> Result<SymbolArray, Error> { | ||
let enum_name = extract_name(prefix, "enum", en.name.as_ref())?; | ||
|
||
let enums = (&en.value) | ||
.iter() | ||
.map(|value| { | ||
let value_name = extract_name(&enum_name, "enum value", value.name.as_ref())?; | ||
Ok((value_name, fd.clone())) | ||
}) | ||
.collect::<Result<Vec<(String, Arc<FileDescriptorProto>)>, Error>>()?; | ||
|
||
let symbols = vec![(enum_name.clone(), fd.clone())] | ||
.into_iter() | ||
.chain(enums.into_iter()) | ||
.collect(); | ||
|
||
Ok(symbols) | ||
} | ||
|
||
fn process_field( | ||
fd: Arc<FileDescriptorProto>, | ||
prefix: &str, | ||
field: &FieldDescriptorProto, | ||
) -> Result<SymbolArray, Error> { | ||
let field_name = extract_name(prefix, "field", field.name.as_ref())?; | ||
Ok(vec![(field_name, fd)]) | ||
} | ||
|
||
fn extract_name( | ||
prefix: &str, | ||
name_type: &str, | ||
maybe_name: Option<&String>, | ||
) -> Result<String, Error> { | ||
match maybe_name { | ||
None => Err(Error::InvalidFileDescriptorSet(format!( | ||
"missing {} name", | ||
name_type | ||
))), | ||
Some(name) => { | ||
if prefix.is_empty() { | ||
Ok(name.to_string()) | ||
} else { | ||
Ok(format!("{}.{}", prefix, name)) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.