Skip to content

Commit

Permalink
Add skip_default_fields option to text_format
Browse files Browse the repository at this point in the history
  • Loading branch information
bouk committed Dec 3, 2024
1 parent 8fd210d commit f54f011
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 56 deletions.
1 change: 0 additions & 1 deletion prost-reflect/src/dynamic/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ impl DynamicMessageFieldSet {
})
}

#[cfg(feature = "serde")]
pub(crate) fn iter_include_default<'a>(
&'a self,
message: &'a MessageDescriptor,
Expand Down
16 changes: 16 additions & 0 deletions prost-reflect/src/dynamic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1200,3 +1200,19 @@ fn type_sizes() {
assert_eq!(std::mem::size_of::<DynamicMessage>(), 40);
assert_eq!(std::mem::size_of::<Value>(), 56);
}

pub(crate) enum Either<L, R> {
Left(L),
Right(R),
}

impl<L: Iterator, R: Iterator<Item = L::Item>> Iterator for Either<L, R> {
type Item = L::Item;

fn next(&mut self) -> Option<Self::Item> {
match self {
Either::Left(left) => left.next(),
Either::Right(right) => right.next(),
}
}
}
82 changes: 29 additions & 53 deletions prost-reflect/src/dynamic/serde/ser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,62 +54,38 @@ fn serialize_dynamic_message_fields<S>(
where
S: SerializeMap,
{
if options.skip_default_fields {
for field in value.fields.iter(&value.desc) {
let (name, value, ref kind) = match field {
ValueAndDescriptor::Field(value, ref field_desc) => {
let name = if options.use_proto_field_name {
field_desc.name()
} else {
field_desc.json_name()
};
(name, value, field_desc.kind())
}
ValueAndDescriptor::Extension(value, ref extension_desc) => {
(extension_desc.json_name(), value, extension_desc.kind())
}
ValueAndDescriptor::Unknown(_) => continue,
};

map.serialize_entry(
name,
&SerializeWrapper {
value: &ValueAndKind {
value: value.as_ref(),
kind,
},
options,
},
)?;
}
let fields = if options.skip_default_fields {
crate::dynamic::Either::Left(value.fields.iter(&value.desc))
} else {
for field in value.fields.iter_include_default(&value.desc) {
let (name, value, ref kind) = match field {
ValueAndDescriptor::Field(value, ref field_desc) => {
let name = if options.use_proto_field_name {
field_desc.name()
} else {
field_desc.json_name()
};
(name, value, field_desc.kind())
}
ValueAndDescriptor::Extension(value, ref extension_desc) => {
(extension_desc.json_name(), value, extension_desc.kind())
}
ValueAndDescriptor::Unknown(_) => continue,
};
crate::dynamic::Either::Right(value.fields.iter_include_default(&value.desc))
};

map.serialize_entry(
name,
&SerializeWrapper {
value: &ValueAndKind {
value: value.as_ref(),
kind,
},
options,
for field in fields {
let (name, value, ref kind) = match field {
ValueAndDescriptor::Field(value, ref field_desc) => {
let name = if options.use_proto_field_name {
field_desc.name()
} else {
field_desc.json_name()
};
(name, value, field_desc.kind())
}
ValueAndDescriptor::Extension(value, ref extension_desc) => {
(extension_desc.json_name(), value, extension_desc.kind())
}
ValueAndDescriptor::Unknown(_) => continue,

Check warning on line 76 in prost-reflect/src/dynamic/serde/ser/mod.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/dynamic/serde/ser/mod.rs#L76

Added line #L76 was not covered by tests
};

map.serialize_entry(
name,

Check warning on line 80 in prost-reflect/src/dynamic/serde/ser/mod.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/dynamic/serde/ser/mod.rs#L80

Added line #L80 was not covered by tests
&SerializeWrapper {
value: &ValueAndKind {
value: value.as_ref(),
kind,

Check warning on line 84 in prost-reflect/src/dynamic/serde/ser/mod.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/dynamic/serde/ser/mod.rs#L84

Added line #L84 was not covered by tests
},
)?;
}
options,

Check warning on line 86 in prost-reflect/src/dynamic/serde/ser/mod.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/dynamic/serde/ser/mod.rs#L86

Added line #L86 was not covered by tests
},
)?;
}

Ok(())
Expand Down
30 changes: 28 additions & 2 deletions prost-reflect/src/dynamic/text_format/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ where
}
}

let fields = message.fields.iter(&message.desc);
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))
};

if self.options.skip_unknown_fields {
self.fmt_delimited(
fields.filter(|f| !matches!(f, ValueAndDescriptor::Unknown(..))),
Expand Down Expand Up @@ -85,7 +90,15 @@ where
write!(self.f, "{}", value)
}
Value::Message(message) => {
if message.fields.iter(&message.desc).all(|f| {
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),

Check warning on line 97 in prost-reflect/src/dynamic/text_format/format.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/dynamic/text_format/format.rs#L97

Added line #L97 was not covered by tests
)
};

if fields.all(|f| {
self.options.skip_unknown_fields && matches!(f, ValueAndDescriptor::Unknown(..))
}) {
self.f.write_str("{}")
Expand Down Expand Up @@ -309,6 +322,7 @@ fn as_any(message: &DynamicMessage) -> Option<(String, DynamicMessage)> {
#[cfg(feature = "text-format")]
mod tests {
use super::*;
use crate::ReflectMessage;

fn fmt_unknown(value: &UnknownFieldSet) -> String {
let mut string = String::new();
Expand Down Expand Up @@ -418,4 +432,16 @@ mod tests {
}"#
);
}

#[test]
fn fmt_include_default() {
let timestamp: prost_types::Timestamp = Default::default();
let message = timestamp.transcode_to_dynamic();

let mut string = String::new();
Writer::new(FormatOptions::new().skip_default_fields(false), &mut string)
.fmt_message(&message)
.unwrap();
assert_eq!(string, "seconds:0,nanos:0");
}
}
14 changes: 14 additions & 0 deletions prost-reflect/src/dynamic/text_format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct FormatOptions {
pretty: bool,
skip_unknown_fields: bool,
expand_any: bool,
skip_default_fields: bool,
}

#[cfg(feature = "text-format")]
Expand Down Expand Up @@ -145,6 +146,18 @@ impl FormatOptions {
self
}

/// Whether to skip fields which have their default value.
///
/// If `true`, any fields for which [`has_field`][DynamicMessage::has_field] returns `false` will
/// not be included. If `false`, they will be included with their default value.
///
/// The default value is `true`.
#[cfg(feature = "text-format")]
pub fn skip_default_fields(mut self, yes: bool) -> Self {
self.skip_default_fields = 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:
Expand Down Expand Up @@ -193,6 +206,7 @@ impl Default for FormatOptions {
pretty: false,
skip_unknown_fields: true,
expand_any: true,
skip_default_fields: true,
}
}
}

0 comments on commit f54f011

Please sign in to comment.