diff --git a/prost-reflect-tests/src/test.proto b/prost-reflect-tests/src/test.proto index 50cb71d4..ea7f5a11 100644 --- a/prost-reflect-tests/src/test.proto +++ b/prost-reflect-tests/src/test.proto @@ -107,3 +107,9 @@ enum EnumWithAlias { message MessageWithAliasedEnum { EnumWithAlias aliased = 1; } + +message IndexOrder { + int32 a = 3; + int32 b = 2; + int32 c = 1; +} diff --git a/prost-reflect-tests/src/text_format.rs b/prost-reflect-tests/src/text_format.rs index d15fddf1..4257a0ee 100644 --- a/prost-reflect-tests/src/text_format.rs +++ b/prost-reflect-tests/src/text_format.rs @@ -10,8 +10,8 @@ use prost_reflect::{text_format::FormatOptions, DynamicMessage, ReflectMessage, use crate::{ proto::{ - contains_group, ComplexType, ContainsGroup, MessageWithAliasedEnum, Point, ScalarArrays, - Scalars, WellKnownTypes, + contains_group, ComplexType, ContainsGroup, IndexOrder, MessageWithAliasedEnum, Point, + ScalarArrays, Scalars, WellKnownTypes, }, test_file_descriptor, }; @@ -353,6 +353,23 @@ fn fmt_group() { ); } +#[test] +fn fmt_index_order() { + let value = IndexOrder { a: 1, b: 2, c: 3 }.transcode_to_dynamic(); + assert_eq!( + value.to_text_format_with_options( + &FormatOptions::new().print_message_fields_in_index_order(true) + ), + "a:1,b:2,c:3" + ); + assert_eq!( + value.to_text_format_with_options( + &FormatOptions::new().print_message_fields_in_index_order(false) + ), + "c:3,b:2,a:1" + ); +} + #[test] fn parse_group() { let value = ContainsGroup { diff --git a/prost-reflect/src/descriptor/api.rs b/prost-reflect/src/descriptor/api.rs index 13e46a2a..9c7aa40e 100644 --- a/prost-reflect/src/descriptor/api.rs +++ b/prost-reflect/src/descriptor/api.rs @@ -756,6 +756,19 @@ impl MessageDescriptor { }) } + pub(crate) fn fields_in_index_order( + &self, + ) -> impl ExactSizeIterator + '_ { + self.inner() + .fields + .iter() + .enumerate() + .map(|(index, _)| FieldDescriptor { + message: self.clone(), + index: index as u32, + }) + } + /// Gets an iterator yielding a [`OneofDescriptor`] for each oneof field defined in this message. pub fn oneofs(&self) -> impl ExactSizeIterator + '_ { indices(&self.inner().oneofs).map(|index| OneofDescriptor { diff --git a/prost-reflect/src/dynamic/fields.rs b/prost-reflect/src/dynamic/fields.rs index 0ae3c6b9..bcddf4f9 100644 --- a/prost-reflect/src/dynamic/fields.rs +++ b/prost-reflect/src/dynamic/fields.rs @@ -9,7 +9,10 @@ use crate::{ ExtensionDescriptor, FieldDescriptor, Kind, MessageDescriptor, OneofDescriptor, Value, }; -use super::unknown::{UnknownField, UnknownFieldSet}; +use super::{ + unknown::{UnknownField, UnknownFieldSet}, + Either, +}; pub(crate) trait FieldDescriptorLike: fmt::Debug { #[cfg(feature = "text-format")] @@ -143,68 +146,55 @@ impl DynamicMessageFieldSet { } } + /// Iterates over the fields in the message. + /// + /// If `include_default` is `true`, fields with their default value will be included. + /// If `index_order` is `true`, fields will be iterated in the order they were defined in the source code. Otherwise, they will be iterated in field number order. pub(crate) fn iter<'a>( &'a self, message: &'a MessageDescriptor, + include_default: bool, + index_order: bool, ) -> impl Iterator> + 'a { - self.fields - .iter() - .filter_map(move |(&number, value)| match value { - ValueOrUnknown::Value(value) => { - if let Some(field) = message.get_field(number) { - if field.has(value) { - Some(ValueAndDescriptor::Field(Cow::Borrowed(value), field)) - } else { - None - } - } else if let Some(extension) = message.get_extension(number) { - if extension.has(value) { - Some(ValueAndDescriptor::Extension( - Cow::Borrowed(value), - extension, - )) - } else { - None - } - } else { - panic!("no field found with number {}", number) - } + let field_descriptors = if index_order { + Either::Left(message.fields_in_index_order()) + } else { + Either::Right(message.fields()) + }; + + let fields = field_descriptors + .filter(move |f| { + if include_default { + !f.supports_presence() || self.has(f) + } else { + self.has(f) } - ValueOrUnknown::Unknown(unknown) => Some(ValueAndDescriptor::Unknown(unknown)), - ValueOrUnknown::Taken => None, }) - } - - pub(crate) fn iter_include_default<'a>( - &'a self, - message: &'a MessageDescriptor, - ) -> impl Iterator> + 'a { - let fields = message - .fields() - .filter(move |f| !f.supports_presence() || self.has(f)) - .map(move |f| ValueAndDescriptor::Field(self.get(&f), f)); - let others = self - .fields - .iter() - .filter_map(move |(&number, value)| match value { - ValueOrUnknown::Value(value) => { - if let Some(extension) = message.get_extension(number) { - if extension.has(value) { - Some(ValueAndDescriptor::Extension( - Cow::Borrowed(value), - extension, - )) + .map(|f| ValueAndDescriptor::Field(self.get(&f), f)); + + let extensions_unknowns = + self.fields + .iter() + .filter_map(move |(&number, value)| match value { + ValueOrUnknown::Value(value) => { + if let Some(extension) = message.get_extension(number) { + if extension.has(value) { + Some(ValueAndDescriptor::Extension( + Cow::Borrowed(value), + extension, + )) + } else { + None + } } else { None } - } else { - None } - } - ValueOrUnknown::Unknown(unknown) => Some(ValueAndDescriptor::Unknown(unknown)), - ValueOrUnknown::Taken => None, - }); - fields.chain(others) + ValueOrUnknown::Unknown(unknown) => Some(ValueAndDescriptor::Unknown(unknown)), + ValueOrUnknown::Taken => None, + }); + + fields.chain(extensions_unknowns) } pub(crate) fn iter_fields<'a>( diff --git a/prost-reflect/src/dynamic/message.rs b/prost-reflect/src/dynamic/message.rs index ce52b7f2..3cd5b20b 100644 --- a/prost-reflect/src/dynamic/message.rs +++ b/prost-reflect/src/dynamic/message.rs @@ -19,7 +19,7 @@ impl Message for DynamicMessage { where Self: Sized, { - for field in self.fields.iter(&self.desc) { + for field in self.fields.iter(&self.desc, false, false) { match field { ValueAndDescriptor::Field(value, field_desc) => { value.encode_field(&field_desc, buf) @@ -61,7 +61,7 @@ impl Message for DynamicMessage { fn encoded_len(&self) -> usize { let mut len = 0; - for field in self.fields.iter(&self.desc) { + for field in self.fields.iter(&self.desc, false, false) { match field { ValueAndDescriptor::Field(value, field_desc) => { len += value.encoded_len(&field_desc); diff --git a/prost-reflect/src/dynamic/serde/ser/mod.rs b/prost-reflect/src/dynamic/serde/ser/mod.rs index 8aba6891..7da55a25 100644 --- a/prost-reflect/src/dynamic/serde/ser/mod.rs +++ b/prost-reflect/src/dynamic/serde/ser/mod.rs @@ -54,11 +54,9 @@ fn serialize_dynamic_message_fields( where S: SerializeMap, { - let fields = if options.skip_default_fields { - crate::dynamic::Either::Left(value.fields.iter(&value.desc)) - } else { - crate::dynamic::Either::Right(value.fields.iter_include_default(&value.desc)) - }; + let fields = value + .fields + .iter(&value.desc, !options.skip_default_fields, false); for field in fields { let (name, value, ref kind) = match field { diff --git a/prost-reflect/src/dynamic/text_format/format.rs b/prost-reflect/src/dynamic/text_format/format.rs index 3b479ba0..eada599f 100644 --- a/prost-reflect/src/dynamic/text_format/format.rs +++ b/prost-reflect/src/dynamic/text_format/format.rs @@ -42,11 +42,11 @@ where } } - let fields = if self.options.skip_default_fields { - crate::dynamic::Either::Left(message.fields.iter(&message.desc)) - } else { - crate::dynamic::Either::Right(message.fields.iter_include_default(&message.desc)) - }; + let fields = message.fields.iter( + &message.desc, + !self.options.skip_default_fields, + self.options.print_message_fields_in_index_order, + ); if self.options.skip_unknown_fields { self.fmt_delimited( @@ -90,13 +90,11 @@ where write!(self.f, "{}", value) } Value::Message(message) => { - let mut fields = if self.options.skip_default_fields { - crate::dynamic::Either::Left(message.fields.iter(&message.desc)) - } else { - crate::dynamic::Either::Right( - message.fields.iter_include_default(&message.desc), - ) - }; + let mut fields = message.fields.iter( + &message.desc, + !self.options.skip_default_fields, + self.options.print_message_fields_in_index_order, + ); if fields.all(|f| { self.options.skip_unknown_fields && matches!(f, ValueAndDescriptor::Unknown(..)) diff --git a/prost-reflect/src/dynamic/text_format/mod.rs b/prost-reflect/src/dynamic/text_format/mod.rs index 982aeae0..6d33e3ff 100644 --- a/prost-reflect/src/dynamic/text_format/mod.rs +++ b/prost-reflect/src/dynamic/text_format/mod.rs @@ -19,6 +19,7 @@ pub struct FormatOptions { skip_unknown_fields: bool, expand_any: bool, skip_default_fields: bool, + print_message_fields_in_index_order: bool, } #[cfg(feature = "text-format")] @@ -158,6 +159,18 @@ impl FormatOptions { self } + /// Whether to print message fields in the order they were defined in source code. + /// + /// If set to `true`, message fields will be printed in the order they were defined in the source code. + /// Otherwise, they will be printed in field number order. + /// + /// The default value is `false`. + #[cfg(feature = "text-format")] + pub fn print_message_fields_in_index_order(mut self, yes: bool) -> Self { + self.print_message_fields_in_index_order = yes; + self + } + /// Whether to use the expanded form of the `google.protobuf.Any` type. /// /// If set to `true`, `Any` fields will use an expanded form: @@ -207,6 +220,7 @@ impl Default for FormatOptions { skip_unknown_fields: true, expand_any: true, skip_default_fields: true, + print_message_fields_in_index_order: false, } } }