use super::*;
#[test]
fn test_reserved_field_name_rejected() {
let field = make_field(
"__buffa_cached_size",
1,
Label::LABEL_OPTIONAL,
Type::TYPE_INT32,
);
let msg = DescriptorProto {
name: Some("BadMessage".to_string()),
field: vec![field],
..Default::default()
};
let mut file = proto3_file("test.proto");
file.package = Some("my.pkg".to_string());
file.message_type = vec![msg];
let config = CodeGenConfig::default();
let result = generate(&[file], &["test.proto".to_string()], &config);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(
err.to_string().contains("__buffa_cached_size"),
"error should mention the field name: {err}"
);
assert!(
err.to_string().contains("my.pkg.BadMessage"),
"error should mention the message name: {err}"
);
}
#[test]
fn test_non_reserved_field_name_accepted() {
let field = make_field("cached_size", 1, Label::LABEL_OPTIONAL, Type::TYPE_INT32);
let msg = DescriptorProto {
name: Some("OkMessage".to_string()),
field: vec![field],
..Default::default()
};
let mut file = proto3_file("test.proto");
file.package = Some("my.pkg".to_string());
file.message_type = vec![msg];
let config = CodeGenConfig::default();
let result = generate(&[file], &["test.proto".to_string()], &config);
assert!(
result.is_ok(),
"cached_size should be allowed as a field name"
);
}
#[test]
fn test_module_name_conflict_detected() {
let mut file = proto3_file("test.proto");
file.package = Some("my.pkg".to_string());
file.message_type = vec![
DescriptorProto {
name: Some("HTTPRequest".to_string()),
..Default::default()
},
DescriptorProto {
name: Some("HttpRequest".to_string()),
..Default::default()
},
];
let config = CodeGenConfig::default();
let result = generate(&[file], &["test.proto".to_string()], &config);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("http_request"),
"should mention module name: {err}"
);
assert!(
err.contains("HTTPRequest"),
"should mention first message: {err}"
);
assert!(
err.contains("HttpRequest"),
"should mention second message: {err}"
);
}
#[test]
fn test_nested_module_name_conflict_detected() {
let parent = DescriptorProto {
name: Some("Parent".to_string()),
nested_type: vec![
DescriptorProto {
name: Some("FOO".to_string()),
..Default::default()
},
DescriptorProto {
name: Some("Foo".to_string()),
..Default::default()
},
],
..Default::default()
};
let mut file = proto3_file("test.proto");
file.package = Some("pkg".to_string());
file.message_type = vec![parent];
let config = CodeGenConfig::default();
let result = generate(&[file], &["test.proto".to_string()], &config);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("foo"), "should mention module name: {err}");
}
#[test]
fn test_different_snake_case_names_no_conflict() {
let mut file = proto3_file("test.proto");
file.package = Some("pkg".to_string());
file.message_type = vec![
DescriptorProto {
name: Some("FooBar".to_string()),
..Default::default()
},
DescriptorProto {
name: Some("FooBaz".to_string()),
..Default::default()
},
];
let config = CodeGenConfig::default();
let result = generate(&[file], &["test.proto".to_string()], &config);
assert!(
result.is_ok(),
"distinct snake_case names should not conflict"
);
}
#[test]
fn test_nested_type_oneof_conflict_detected() {
let msg = DescriptorProto {
name: Some("Parent".to_string()),
nested_type: vec![DescriptorProto {
name: Some("MyField".to_string()),
..Default::default()
}],
oneof_decl: vec![OneofDescriptorProto {
name: Some("my_field".to_string()),
..Default::default()
}],
field: vec![{
let mut f = make_field("val", 1, Label::LABEL_OPTIONAL, Type::TYPE_STRING);
f.oneof_index = Some(0);
f
}],
..Default::default()
};
let mut file = proto3_file("test.proto");
file.package = Some("pkg".to_string());
file.message_type = vec![msg];
let config = CodeGenConfig::default();
let result = generate(&[file], &["test.proto".to_string()], &config);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("MyField"),
"should mention the conflicting name: {err}"
);
assert!(
err.contains("my_field"),
"should mention the oneof name: {err}"
);
}
#[test]
fn test_nested_type_oneof_no_conflict() {
let msg = DescriptorProto {
name: Some("Parent".to_string()),
nested_type: vec![DescriptorProto {
name: Some("Inner".to_string()),
..Default::default()
}],
oneof_decl: vec![OneofDescriptorProto {
name: Some("my_field".to_string()),
..Default::default()
}],
field: vec![{
let mut f = make_field("val", 1, Label::LABEL_OPTIONAL, Type::TYPE_STRING);
f.oneof_index = Some(0);
f
}],
..Default::default()
};
let mut file = proto3_file("test.proto");
file.package = Some("pkg".to_string());
file.message_type = vec![msg];
let config = CodeGenConfig::default();
let result = generate(&[file], &["test.proto".to_string()], &config);
assert!(result.is_ok(), "Inner and MyField should not conflict");
}
#[test]
fn test_view_name_conflict_detected() {
let mut file = proto3_file("test.proto");
file.package = Some("pkg".to_string());
file.message_type = vec![
DescriptorProto {
name: Some("Foo".to_string()),
..Default::default()
},
DescriptorProto {
name: Some("FooView".to_string()),
..Default::default()
},
];
let config = CodeGenConfig::default(); let result = generate(&[file], &["test.proto".to_string()], &config);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("Foo"), "should mention owned message: {err}");
assert!(
err.contains("FooView"),
"should mention view collision: {err}"
);
}
#[test]
fn test_view_name_conflict_not_checked_when_views_disabled() {
let mut file = proto3_file("test.proto");
file.package = Some("pkg".to_string());
file.message_type = vec![
DescriptorProto {
name: Some("Foo".to_string()),
..Default::default()
},
DescriptorProto {
name: Some("FooView".to_string()),
..Default::default()
},
];
let config = CodeGenConfig {
generate_views: false,
..Default::default()
};
let result = generate(&[file], &["test.proto".to_string()], &config);
assert!(result.is_ok(), "no conflict when views are disabled");
}
#[test]
fn test_proto3_optional_field_name_matches_nested_enum_no_conflict() {
let msg = DescriptorProto {
name: Some("StringFieldMatcher".to_string()),
enum_type: vec![EnumDescriptorProto {
name: Some("MatchOperator".to_string()),
value: vec![
enum_value("MATCH_OPERATOR_UNKNOWN", 0),
enum_value("MATCH_OPERATOR_EXACT_MATCH", 1),
],
..Default::default()
}],
oneof_decl: vec![OneofDescriptorProto {
name: Some("_match_operator".to_string()),
..Default::default()
}],
field: vec![{
let mut f = make_field("match_operator", 4, Label::LABEL_OPTIONAL, Type::TYPE_ENUM);
f.type_name = Some(".minimal.StringFieldMatcher.MatchOperator".to_string());
f.oneof_index = Some(0);
f.proto3_optional = Some(true);
f
}],
..Default::default()
};
let mut file = proto3_file("test.proto");
file.package = Some("minimal".to_string());
file.message_type = vec![msg];
let config = CodeGenConfig::default();
let result = generate(&[file], &["test.proto".to_string()], &config);
assert!(
result.is_ok(),
"synthetic oneof should not conflict with nested enum: {}",
result.unwrap_err()
);
}
#[test]
fn test_message_named_type_with_nested() {
let mut file = proto3_file("type_test.proto");
file.package = Some("google.api.expr.v1alpha1".to_string());
file.message_type.push(DescriptorProto {
name: Some("Type".to_string()),
field: vec![FieldDescriptorProto {
name: Some("primitive".to_string()),
number: Some(1),
label: Some(Label::LABEL_OPTIONAL),
r#type: Some(Type::TYPE_ENUM),
type_name: Some(".google.api.expr.v1alpha1.Type.PrimitiveType".to_string()),
..Default::default()
}],
nested_type: vec![],
enum_type: vec![EnumDescriptorProto {
name: Some("PrimitiveType".to_string()),
value: vec![
enum_value("PRIMITIVE_TYPE_UNSPECIFIED", 0),
enum_value("BOOL", 1),
],
..Default::default()
}],
..Default::default()
});
let config = CodeGenConfig {
generate_views: false,
..Default::default()
};
let result = generate(&[file], &["type_test.proto".to_string()], &config);
let files = result.expect("message named Type should generate valid code");
let content = &files[0].content;
assert!(
content.contains("pub struct Type"),
"missing struct Type: {content}"
);
assert!(
content.contains("pub mod r#type"),
"missing r#type module: {content}"
);
}
#[test]
fn test_message_with_oneof_field_named_type() {
let mut file = proto3_file("checked.proto");
file.package = Some("google.api.expr.v1alpha1".to_string());
file.message_type.push(DescriptorProto {
name: Some("Type".to_string()),
field: vec![
FieldDescriptorProto {
name: Some("message_type".to_string()),
number: Some(9),
label: Some(Label::LABEL_OPTIONAL),
r#type: Some(Type::TYPE_STRING),
oneof_index: Some(0),
..Default::default()
},
FieldDescriptorProto {
name: Some("type".to_string()),
number: Some(11),
label: Some(Label::LABEL_OPTIONAL),
r#type: Some(Type::TYPE_MESSAGE),
type_name: Some(".google.api.expr.v1alpha1.Type".to_string()),
oneof_index: Some(0),
..Default::default()
},
],
oneof_decl: vec![OneofDescriptorProto {
name: Some("type_kind".to_string()),
..Default::default()
}],
..Default::default()
});
let config = CodeGenConfig {
generate_views: false,
..Default::default()
};
let result = generate(&[file], &["checked.proto".to_string()], &config);
let files = result.expect("Type message with oneof 'type' field should generate");
let content = &files[0].content;
assert!(
content.contains("pub struct Type"),
"missing struct Type: {content}"
);
}