Implement `is_known` method on the `Enum` trait

This is equivalent to C++'s `_IsValid` functions. Since proto3 enums are open,
`is_known` seems to be a better name than the misleading `is_valid`.

C++-specific Protocol Buffers documentation already uses "known fields" and
"unknown enum values" expressions.

PiperOrigin-RevId: 630344180
pull/16702/head
Adrian Sadłocha 2024-05-03 03:48:49 -07:00 committed by Copybara-Service
parent d9ff109888
commit 733b9c54e9
4 changed files with 44 additions and 13 deletions

View File

@ -19,9 +19,16 @@ use std::{
/// representation as erased enums in the runtime.
/// - For C++, this is `proto2::RepeatedField<c_int>`
/// - For UPB, this is an array compatible with `int32`
pub unsafe trait Enum {
pub unsafe trait Enum: TryFrom<i32> {
/// The name of the enum.
const NAME: &'static str;
/// Returns `true` if the given numeric value matches one of the `Self`'s
/// defined values.
///
/// If `Self` is a closed enum, then `TryFrom<i32>` for `value` succeeds if
/// and only if this function returns `true`.
fn is_known(value: i32) -> bool;
}
/// An integer value wasn't known for an enum while converting.

View File

@ -22,7 +22,7 @@ use std::fmt;
/// These are the items protobuf users can access directly.
#[doc(hidden)]
pub mod __public {
pub use crate::r#enum::UnknownEnumValue;
pub use crate::r#enum::{Enum, UnknownEnumValue};
pub use crate::map::{Map, MapIter, MapMut, MapView, ProxiedInMapValue};
pub use crate::optional::Optional;
pub use crate::proto;

View File

@ -9,6 +9,7 @@
use enums_proto::*;
use googletest::prelude::*;
use protobuf::Enum;
use unittest_proto::*;
#[test]
@ -167,3 +168,24 @@ fn test_enum_conversion_failure_impls_std_error() {
let err = TestSparseEnum::try_from(1).unwrap_err();
let _test_compiles: &dyn std::error::Error = &err;
}
#[test]
fn test_is_known_for_closed_enum() {
assert_that!(test_all_types::NestedEnum::is_known(-2), eq(false));
assert_that!(test_all_types::NestedEnum::is_known(-1), eq(true));
assert_that!(test_all_types::NestedEnum::is_known(0), eq(false));
assert_that!(test_all_types::NestedEnum::is_known(1), eq(true));
assert_that!(test_all_types::NestedEnum::is_known(2), eq(true));
assert_that!(test_all_types::NestedEnum::is_known(3), eq(true));
assert_that!(test_all_types::NestedEnum::is_known(4), eq(false));
}
#[test]
fn test_is_known_for_open_enum() {
assert_that!(TestEnumWithNumericNames::is_known(-1), eq(false));
assert_that!(TestEnumWithNumericNames::is_known(0), eq(true));
assert_that!(TestEnumWithNumericNames::is_known(1), eq(true));
assert_that!(TestEnumWithNumericNames::is_known(2), eq(true));
assert_that!(TestEnumWithNumericNames::is_known(3), eq(true));
assert_that!(TestEnumWithNumericNames::is_known(4), eq(false));
}

View File

@ -325,23 +325,21 @@ void GenerateEnumDefinition(Context& ctx, const EnumDescriptor& desc) {
// The default value of an enum is the first listed value.
// The compiler checks that this is equal to 0 for open enums.
{"default_int_value", absl::StrCat(desc.value(0)->number())},
{"known_values_pattern",
// TODO: Check validity in UPB/C++.
absl::StrJoin(values, "|",
[](std::string* o, const RustEnumValue& val) {
absl::StrAppend(o, val.number);
})},
{"impl_from_i32",
[&] {
if (desc.is_closed()) {
ctx.Emit({{"name", name},
{"known_values_pattern",
// TODO: Check validity in UPB/C++.
absl::StrJoin(
values, "|",
[](std::string* o, const RustEnumValue& val) {
absl::StrAppend(o, val.number);
})}},
R"rs(
ctx.Emit(R"rs(
impl $std$::convert::TryFrom<i32> for $name$ {
type Error = $pb$::UnknownEnumValue<Self>;
fn try_from(val: i32) -> Result<$name$, Self::Error> {
if matches!(val, $known_values_pattern$) {
if <Self as $pbi$::Enum>::is_known(val) {
Ok(Self(val))
} else {
Err($pb$::UnknownEnumValue::new($pbi$::Private, val))
@ -350,7 +348,7 @@ void GenerateEnumDefinition(Context& ctx, const EnumDescriptor& desc) {
}
)rs");
} else {
ctx.Emit({{"name", name}}, R"rs(
ctx.Emit(R"rs(
impl $std$::convert::From<i32> for $name$ {
fn from(val: i32) -> $name$ {
Self(val)
@ -457,6 +455,10 @@ void GenerateEnumDefinition(Context& ctx, const EnumDescriptor& desc) {
// SAFETY: this is an enum type
unsafe impl $pbi$::Enum for $name$ {
const NAME: &'static str = "$name$";
fn is_known(value: i32) -> bool {
matches!(value, $known_values_pattern$)
}
}
$impl_proxied_in_map$