use super::*;
fn idiomatic_config() -> CodeGenConfig {
CodeGenConfig {
idiomatic_enum_aliases: true,
..Default::default()
}
}
fn enum_file(file: &str, name: &str, values: Vec<EnumValueDescriptorProto>) -> FileDescriptorProto {
let mut f = proto3_file(file);
f.enum_type.push(EnumDescriptorProto {
name: Some(name.to_string()),
value: values,
..Default::default()
});
f
}
#[test]
fn aliases_on_by_default() {
let file = enum_file(
"s.proto",
"RuleLevel",
vec![
enum_value("RULE_LEVEL_UNKNOWN", 0),
enum_value("RULE_LEVEL_HIGH", 1),
],
);
let files = generate(&[file], &["s.proto".to_string()], &CodeGenConfig::default()).unwrap();
let c = joined(&files);
assert!(c.contains("RULE_LEVEL_HIGH = 1"), "{c}");
assert!(
c.contains("const High: Self = Self::RULE_LEVEL_HIGH"),
"{c}"
);
}
#[test]
fn aliases_can_be_disabled() {
let file = enum_file(
"s.proto",
"RuleLevel",
vec![
enum_value("RULE_LEVEL_UNKNOWN", 0),
enum_value("RULE_LEVEL_HIGH", 1),
],
);
let config = CodeGenConfig {
idiomatic_enum_aliases: false,
..Default::default()
};
let files = generate(&[file], &["s.proto".to_string()], &config).unwrap();
let c = joined(&files);
assert!(c.contains("RULE_LEVEL_HIGH = 1"), "{c}");
assert!(
!c.contains("const High"),
"no idiomatic alias when disabled: {c}"
);
}
#[test]
fn aliases_strip_prefix_and_camel_case() {
let file = enum_file(
"s.proto",
"RuleLevel",
vec![
enum_value("RULE_LEVEL_UNKNOWN", 0),
enum_value("RULE_LEVEL_HIGH", 1),
enum_value("RULE_LEVEL_CRITICAL", 2),
],
);
let (files, warnings) =
generate_with_diagnostics(&[file], &["s.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(c.contains("RULE_LEVEL_HIGH = 1"), "{c}");
assert!(
c.contains("const Unknown: Self = Self::RULE_LEVEL_UNKNOWN"),
"{c}"
);
assert!(
c.contains("const High: Self = Self::RULE_LEVEL_HIGH"),
"{c}"
);
assert!(
c.contains("const Critical: Self = Self::RULE_LEVEL_CRITICAL"),
"{c}"
);
assert!(warnings.is_empty(), "{warnings:?}");
}
#[test]
fn aliases_without_matching_prefix_use_full_name() {
let file = enum_file(
"c.proto",
"Color",
vec![enum_value("RED", 0), enum_value("DARK_GREEN", 1)],
);
let files = generate(&[file], &["c.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(c.contains("const Red: Self = Self::RED"), "{c}");
assert!(
c.contains("const DarkGreen: Self = Self::DARK_GREEN"),
"{c}"
);
}
#[test]
fn lossy_collision_suppresses_entire_enum() {
let file = enum_file(
"s.proto",
"Weird",
vec![
enum_value("FOO_BAR", 0),
enum_value("FOO__BAR", 1), enum_value("BAZ", 2),
],
);
let (files, warnings) =
generate_with_diagnostics(&[file], &["s.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(
!c.contains("const Baz"),
"clean value must be suppressed too: {c}"
);
assert!(!c.contains("const FooBar"), "{c}");
assert!(
c.contains("Idiomatic CamelCase aliases are not generated for this enum"),
"{c}"
);
assert_eq!(warnings.len(), 1, "{warnings:?}");
let w = warnings[0].to_string();
assert!(
w.contains("Weird")
&& w.contains("FOO_BAR")
&& w.contains("FOO__BAR")
&& w.contains("FooBar"),
"{w}"
);
match &warnings[0] {
CodeGenWarning::IdiomaticAliasesSuppressed {
enum_name,
conflicts,
invalid,
..
} => {
assert_eq!(enum_name, "Weird");
assert!(invalid.is_empty(), "{invalid:?}");
assert!(
conflicts.iter().any(|c| c.camel_target == "FooBar"
&& c.proto_values.iter().any(|n| n == "FOO_BAR")
&& c.proto_values.iter().any(|n| n == "FOO__BAR")),
"{conflicts:?}"
);
}
other => panic!("expected IdiomaticAliasesSuppressed, got {other:?}"),
}
}
#[test]
fn mixed_convention_silent_shadow_is_detected() {
let file = enum_file(
"s.proto",
"Mix",
vec![enum_value("FOO_BAR", 0), enum_value("FooBar", 1)],
);
let (files, warnings) =
generate_with_diagnostics(&[file], &["s.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(
c.contains("Use the `SHOUTY_SNAKE_CASE` variants directly"),
"{c}"
);
assert_eq!(warnings.len(), 1, "{warnings:?}");
let w = warnings[0].to_string();
assert!(w.contains("FOO_BAR") && w.contains("FooBar"), "{w}");
}
#[test]
fn strip_leading_digit_falls_back_to_unstripped() {
let file = enum_file(
"v.proto",
"Version",
vec![
enum_value("VERSION_UNKNOWN", 0),
enum_value("VERSION_2", 1), enum_value("VERSION_3", 2),
],
);
let (files, warnings) =
generate_with_diagnostics(&[file], &["v.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(
c.contains("const VersionUnknown: Self = Self::VERSION_UNKNOWN"),
"{c}"
);
assert!(c.contains("const Version2: Self = Self::VERSION_2"), "{c}");
assert!(c.contains("const Version3: Self = Self::VERSION_3"), "{c}");
assert!(
warnings.is_empty(),
"fallback should not warn: {warnings:?}"
);
}
#[test]
fn strip_empty_remainder_falls_back_to_unstripped() {
let file = enum_file(
"f.proto",
"Foo",
vec![enum_value("FOO_UNSET", 0), enum_value("FOO_", 1)],
);
let (files, warnings) =
generate_with_diagnostics(&[file], &["f.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(c.contains("const FooUnset: Self = Self::FOO_UNSET"), "{c}");
assert!(c.contains("const Foo: Self = Self::FOO_"), "{c}");
assert!(warnings.is_empty(), "{warnings:?}");
}
#[test]
fn keyword_alias_is_escaped() {
let file = enum_file(
"k.proto",
"Kind",
vec![enum_value("KIND_UNKNOWN", 0), enum_value("KIND_SELF", 1)],
);
let files = generate(&[file], &["k.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(c.contains("const Self_: Self = Self::KIND_SELF"), "{c}");
}
#[test]
fn strip_decided_for_whole_enum_not_per_value() {
let file = enum_file(
"m.proto",
"Mode",
vec![enum_value("MODE_BAR", 0), enum_value("OTHER_VALUE", 1)],
);
let files = generate(&[file], &["m.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(c.contains("const ModeBar: Self = Self::MODE_BAR"), "{c}");
assert!(
c.contains("const OtherValue: Self = Self::OTHER_VALUE"),
"{c}"
);
assert!(!c.contains("const Bar"), "prefix must not be stripped: {c}");
}
#[test]
fn already_camel_with_acronym_is_skipped_not_duplicated() {
let file = enum_file(
"s.proto",
"Shape",
vec![enum_value("MyValue", 0), enum_value("OtherValue", 1)],
);
let files = generate(&[file], &["s.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(!c.contains("Myvalue"), "must not emit a mangled alias: {c}");
assert!(!c.contains("impl Shape {"), "all aliases redundant: {c}");
}
#[test]
fn alias_const_doc_links_variant_not_duplicates_proto_doc() {
let file = enum_file(
"s.proto",
"RuleLevel",
vec![
enum_value("RULE_LEVEL_UNKNOWN", 0),
enum_value("RULE_LEVEL_HIGH", 1),
],
);
let files = generate(&[file], &["s.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(
c.contains("Idiomatic alias for [`Self::RULE_LEVEL_HIGH`]"),
"{c}"
);
}
#[test]
fn redundant_alias_is_skipped() {
let file = enum_file(
"s.proto",
"State",
vec![enum_value("Active", 0), enum_value("Inactive", 1)],
);
let (files, warnings) =
generate_with_diagnostics(&[file], &["s.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(
!c.contains("impl State {"),
"no alias impl block expected: {c}"
);
assert!(warnings.is_empty(), "{warnings:?}");
}
#[test]
fn allow_alias_values_get_idiomatic_aliases() {
let mut file = proto3_file("code.proto");
file.enum_type.push(EnumDescriptorProto {
name: Some("Code".to_string()),
value: vec![
enum_value("CODE_OK", 0),
enum_value("CODE_SUCCESS", 0), enum_value("CODE_ERROR", 1),
],
options: (crate::generated::descriptor::EnumOptions {
allow_alias: Some(true),
..Default::default()
})
.into(),
..Default::default()
});
let files = generate(&[file], &["code.proto".to_string()], &idiomatic_config()).unwrap();
let c = joined(&files);
assert!(c.contains("const Ok: Self = Self::CODE_OK"), "{c}");
assert!(c.contains("const Success: Self = Self::CODE_OK"), "{c}");
assert!(c.contains("const Error: Self = Self::CODE_ERROR"), "{c}");
}