use super::*;
fn simple_msg_file(proto_name: &str) -> FileDescriptorProto {
let mut file = proto3_file(proto_name);
file.message_type.push(DescriptorProto {
name: Some("Item".to_string()),
field: vec![
make_field("name", 1, Label::LABEL_OPTIONAL, Type::TYPE_STRING),
make_field("count", 2, Label::LABEL_OPTIONAL, Type::TYPE_INT32),
],
..Default::default()
});
file
}
#[test]
fn test_owned_view_wrapper_struct_and_value_accessors() {
let files = generate(
&[simple_msg_file("item.proto")],
&["item.proto".to_string()],
&CodeGenConfig::default(),
)
.expect("should generate");
let content = &joined(&files);
assert!(
content.contains("pub struct ItemOwnedView(::buffa::OwnedView<ItemView<'static>>)"),
"missing ItemOwnedView wrapper struct: {content}"
);
assert!(
content.contains("pub fn name(&self) -> &'_ str"),
"implicit-presence string accessor must return &str by value: {content}"
);
assert!(
content.contains("pub fn count(&self) -> i32"),
"scalar accessor must return i32 by value: {content}"
);
for needle in [
"pub fn decode(",
"pub fn decode_with_options(",
"pub fn from_owned(",
"pub fn view(&self) -> &ItemView<'_>",
"pub fn to_owned_message(",
"pub fn into_bytes(",
] {
assert!(content.contains(needle), "missing `{needle}`: {content}");
}
assert!(
content.contains("From<::buffa::OwnedView<ItemView<'static>>> for ItemOwnedView"),
"missing From<OwnedView> conversion: {content}"
);
assert!(
content.contains("From<ItemOwnedView> for ::buffa::OwnedView<ItemView<'static>>"),
"missing Into-OwnedView conversion: {content}"
);
assert!(
content.contains("pub use self::__buffa::view::ItemOwnedView"),
"missing natural-path re-export of the wrapper: {content}"
);
assert!(
content.contains("impl ::buffa::HasMessageView for"),
"missing HasMessageView impl: {content}"
);
assert!(
content.contains("type ViewHandle = ItemOwnedView"),
"HasMessageView::ViewHandle must name the wrapper: {content}"
);
assert!(
content.contains("AsRef<::buffa::OwnedView<ItemView<'static>>> for ItemOwnedView"),
"missing AsRef impl on the wrapper: {content}"
);
}
#[test]
fn test_owned_view_optional_string_accessor_returns_option() {
let mut file = proto3_file("opt.proto");
file.message_type.push(DescriptorProto {
name: Some("Msg".to_string()),
field: vec![FieldDescriptorProto {
name: Some("label".to_string()),
number: Some(1),
label: Some(Label::LABEL_OPTIONAL),
r#type: Some(Type::TYPE_STRING),
proto3_optional: Some(true),
oneof_index: Some(0),
..Default::default()
}],
oneof_decl: vec![OneofDescriptorProto {
name: Some("_label".to_string()),
..Default::default()
}],
..Default::default()
});
let files = generate(
&[file],
&["opt.proto".to_string()],
&CodeGenConfig::default(),
)
.expect("should generate");
let content = &joined(&files);
assert!(
content.contains("pub fn label(&self) -> ::core::option::Option<&'_ str>"),
"explicit-presence string accessor must return Option<&str>: {content}"
);
}
#[test]
fn test_owned_view_message_repeated_and_map_accessors_return_refs() {
let mut file = proto3_file("containers.proto");
file.message_type.push(DescriptorProto {
name: Some("Inner".to_string()),
field: vec![make_field("v", 1, Label::LABEL_OPTIONAL, Type::TYPE_INT32)],
..Default::default()
});
let mut msg_field = make_field("inner", 1, Label::LABEL_OPTIONAL, Type::TYPE_MESSAGE);
msg_field.type_name = Some(".Inner".to_string());
let mut map_field = make_field("labels", 3, Label::LABEL_REPEATED, Type::TYPE_MESSAGE);
map_field.type_name = Some(".Holder.LabelsEntry".to_string());
let map_entry = DescriptorProto {
name: Some("LabelsEntry".to_string()),
field: vec![
make_field("key", 1, Label::LABEL_OPTIONAL, Type::TYPE_STRING),
make_field("value", 2, Label::LABEL_OPTIONAL, Type::TYPE_STRING),
],
options: MessageOptions {
map_entry: Some(true),
..Default::default()
}
.into(),
..Default::default()
};
file.message_type.push(DescriptorProto {
name: Some("Holder".to_string()),
field: vec![
msg_field,
make_field("tags", 2, Label::LABEL_REPEATED, Type::TYPE_STRING),
map_field,
],
nested_type: vec![map_entry],
..Default::default()
});
let files = generate(
&[file],
&["containers.proto".to_string()],
&CodeGenConfig::default(),
)
.expect("should generate");
let content = &joined(&files);
assert!(
content.contains("pub fn inner(&self) -> &::buffa::MessageFieldView<")
|| content
.contains("pub fn inner(\n &self,\n ) -> &::buffa::MessageFieldView<"),
"message-field accessor must return &MessageFieldView: {content}"
);
assert!(
content.contains("&::buffa::RepeatedView<'_, &'_ str>"),
"repeated-string accessor must return &RepeatedView<'_, &str>: {content}"
);
assert!(
content.contains("&::buffa::MapView<'_, &'_ str, &'_ str>"),
"map accessor must return &MapView<'_, &str, &str>: {content}"
);
assert!(
!content.contains("LabelsEntryOwnedView"),
"map-entry synthetic message must not get an owned-view wrapper: {content}"
);
}
#[test]
fn test_owned_view_oneof_accessor_returns_option_ref() {
let mut file = proto3_file("oneof.proto");
file.message_type.push(DescriptorProto {
name: Some("Request".to_string()),
field: vec![
FieldDescriptorProto {
name: Some("count".to_string()),
number: Some(1),
label: Some(Label::LABEL_OPTIONAL),
r#type: Some(Type::TYPE_INT32),
oneof_index: Some(0),
..Default::default()
},
FieldDescriptorProto {
name: Some("text".to_string()),
number: Some(2),
label: Some(Label::LABEL_OPTIONAL),
r#type: Some(Type::TYPE_STRING),
oneof_index: Some(0),
..Default::default()
},
],
oneof_decl: vec![OneofDescriptorProto {
name: Some("payload".to_string()),
..Default::default()
}],
..Default::default()
});
let files = generate(
&[file],
&["oneof.proto".to_string()],
&CodeGenConfig::default(),
)
.expect("should generate");
let content = &joined(&files);
assert!(
content.contains("pub fn payload("),
"missing oneof accessor: {content}"
);
assert!(
content.contains(".payload.as_ref()"),
"oneof accessor must return Option<&Kind> via as_ref(): {content}"
);
}
#[test]
fn test_owned_view_reserved_field_name_suppressed_with_warning() {
let mut file = proto3_file("reserved.proto");
file.message_type.push(DescriptorProto {
name: Some("Blob".to_string()),
field: vec![
make_field("bytes", 1, Label::LABEL_OPTIONAL, Type::TYPE_STRING),
make_field("ok", 2, Label::LABEL_OPTIONAL, Type::TYPE_STRING),
],
..Default::default()
});
let (files, warnings) = generate_with_diagnostics(
&[file],
&["reserved.proto".to_string()],
&CodeGenConfig::default(),
)
.expect("should generate");
let content = &joined(&files);
assert!(
content.contains("pub fn bytes(&self) -> &::buffa::bytes::Bytes"),
"inherent bytes() must survive: {content}"
);
assert!(
!content.contains("pub fn bytes(&self) -> &'_ str"),
"colliding field accessor must be suppressed: {content}"
);
assert!(
content.contains("pub fn ok(&self) -> &'_ str"),
"non-colliding accessor must still be generated: {content}"
);
assert!(
warnings.iter().any(|w| matches!(
w,
CodeGenWarning::OwnedViewAccessorSuppressed { wrapper_name, field_name }
if wrapper_name == "BlobOwnedView" && field_name == "bytes"
)),
"expected an OwnedViewAccessorSuppressed warning: {warnings:?}"
);
}
#[test]
fn test_owned_view_not_generated_without_views() {
let files = generate(
&[simple_msg_file("noview.proto")],
&["noview.proto".to_string()],
&CodeGenConfig {
generate_views: false,
..CodeGenConfig::default()
},
)
.expect("should generate");
let content = &joined(&files);
assert!(
!content.contains("OwnedView"),
"no owned-view wrapper without generate_views: {content}"
);
}
#[test]
fn test_owned_view_serialize_impl_gated_on_json() {
let with_json = generate(
&[simple_msg_file("json_on.proto")],
&["json_on.proto".to_string()],
&CodeGenConfig {
generate_json: true,
..CodeGenConfig::default()
},
)
.expect("should generate");
let content = &joined(&with_json);
assert!(
content.contains("impl ::serde::Serialize for ItemOwnedView"),
"json config must emit a Serialize forwarding impl for the wrapper: {content}"
);
let without_json = generate(
&[simple_msg_file("json_off.proto")],
&["json_off.proto".to_string()],
&CodeGenConfig::default(),
)
.expect("should generate");
let content = &joined(&without_json);
assert!(
!content.contains("impl ::serde::Serialize for ItemOwnedView"),
"no wrapper Serialize impl without generate_json: {content}"
);
}