use core::fmt;
use core::hash::Hash;
pub trait Enumeration: Clone + Copy + PartialEq + Eq + Hash + fmt::Debug {
fn from_i32(value: i32) -> Option<Self>;
fn to_i32(&self) -> i32;
fn proto_name(&self) -> &'static str;
fn from_proto_name(_name: &str) -> Option<Self> {
None
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum EnumValue<E: Enumeration> {
Known(E),
Unknown(i32),
}
impl<E: Enumeration> EnumValue<E> {
#[inline]
pub fn to_i32(&self) -> i32 {
match self {
Self::Known(e) => e.to_i32(),
Self::Unknown(v) => *v,
}
}
#[inline]
pub fn is_known(&self) -> bool {
matches!(self, Self::Known(_))
}
#[inline]
pub fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown(_))
}
#[inline]
pub fn as_known(&self) -> Option<E> {
match self {
Self::Known(e) => Some(*e),
Self::Unknown(_) => None,
}
}
}
impl<E: Enumeration> From<i32> for EnumValue<E> {
fn from(value: i32) -> Self {
match E::from_i32(value) {
Some(e) => Self::Known(e),
None => Self::Unknown(value),
}
}
}
impl<E: Enumeration> From<E> for EnumValue<E> {
fn from(value: E) -> Self {
Self::Known(value)
}
}
impl<E: Enumeration> PartialEq<E> for EnumValue<E> {
fn eq(&self, other: &E) -> bool {
match self {
Self::Known(e) => e == other,
Self::Unknown(_) => false,
}
}
}
impl<E: Enumeration> fmt::Debug for EnumValue<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Known(e) => write!(f, "{:?}", e),
Self::Unknown(v) => write!(f, "Unknown({v})"),
}
}
}
impl<E: Enumeration> fmt::Display for EnumValue<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Known(e) => f.write_str(e.proto_name()),
Self::Unknown(v) => write!(f, "{v}"),
}
}
}
#[cfg(feature = "json")]
impl<E: Enumeration> serde::Serialize for EnumValue<E> {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
match self {
Self::Known(e) => s.serialize_str(e.proto_name()),
Self::Unknown(v) => s.serialize_i32(*v),
}
}
}
#[cfg(feature = "json")]
impl<'de, E: Enumeration> serde::Deserialize<'de> for EnumValue<E> {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct EnumValueVisitor<E>(core::marker::PhantomData<E>);
impl<'de, E: Enumeration> serde::de::Visitor<'de> for EnumValueVisitor<E> {
type Value = EnumValue<E>;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a protobuf enum name string or integer value")
}
fn visit_unit<Err: serde::de::Error>(self) -> Result<EnumValue<E>, Err> {
Ok(EnumValue::from(0))
}
fn visit_str<Err: serde::de::Error>(self, v: &str) -> Result<EnumValue<E>, Err> {
match E::from_proto_name(v) {
Some(e) => Ok(EnumValue::Known(e)),
None => {
#[cfg(all(feature = "std", feature = "json"))]
if crate::json::ignore_unknown_enum_values() {
return Ok(EnumValue::from(0));
}
Err(serde::de::Error::unknown_variant(v, &[]))
}
}
}
fn visit_i64<Err: serde::de::Error>(self, v: i64) -> Result<EnumValue<E>, Err> {
let n = i32::try_from(v).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Signed(v),
&"an i32 enum value",
)
})?;
Ok(EnumValue::from(n))
}
fn visit_u64<Err: serde::de::Error>(self, v: u64) -> Result<EnumValue<E>, Err> {
let n = i32::try_from(v).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Unsigned(v),
&"an i32 enum value",
)
})?;
Ok(EnumValue::from(n))
}
}
d.deserialize_any(EnumValueVisitor(core::marker::PhantomData))
}
}
impl<E: Enumeration> Default for EnumValue<E> {
fn default() -> Self {
Self::from(0)
}
}
#[cfg(feature = "arbitrary")]
impl<'a, E: Enumeration + arbitrary::Arbitrary<'a>> arbitrary::Arbitrary<'a> for EnumValue<E> {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
if u.ratio(3, 4)? {
Ok(EnumValue::Known(E::arbitrary(u)?))
} else {
Ok(EnumValue::Unknown(i32::arbitrary(u)?))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum Color {
Red,
Green,
Blue,
}
impl Enumeration for Color {
fn from_i32(v: i32) -> Option<Self> {
match v {
0 => Some(Color::Red),
1 => Some(Color::Green),
2 => Some(Color::Blue),
_ => None,
}
}
fn to_i32(&self) -> i32 {
*self as i32
}
fn proto_name(&self) -> &'static str {
match self {
Color::Red => "RED",
Color::Green => "GREEN",
Color::Blue => "BLUE",
}
}
fn from_proto_name(name: &str) -> Option<Self> {
match name {
"RED" => Some(Color::Red),
"GREEN" => Some(Color::Green),
"BLUE" => Some(Color::Blue),
_ => None,
}
}
}
#[test]
fn from_i32_known_value_produces_known_variant() {
let v: EnumValue<Color> = EnumValue::from(1);
assert_eq!(v, EnumValue::Known(Color::Green));
}
#[test]
fn from_i32_unknown_values_produce_unknown_variant() {
for v in [99, -1, i32::MIN, i32::MAX] {
let ev: EnumValue<Color> = EnumValue::from(v);
assert_eq!(ev, EnumValue::Unknown(v), "from({v})");
assert_eq!(ev.to_i32(), v, "to_i32 roundtrip for {v}");
}
}
#[test]
fn from_enum_variant_produces_known() {
let v = EnumValue::from(Color::Blue);
assert_eq!(v, EnumValue::Known(Color::Blue));
}
#[test]
fn to_i32_known_returns_variant_value() {
let v = EnumValue::Known(Color::Green);
assert_eq!(v.to_i32(), 1);
}
#[test]
fn to_i32_unknown_returns_raw_value() {
let v: EnumValue<Color> = EnumValue::Unknown(42);
assert_eq!(v.to_i32(), 42);
}
#[test]
fn to_i32_unknown_negative_returns_raw_value() {
let v: EnumValue<Color> = EnumValue::Unknown(-1);
assert_eq!(v.to_i32(), -1);
}
#[test]
fn as_known_returns_some_for_known_variant() {
let v = EnumValue::Known(Color::Red);
assert_eq!(v.as_known(), Some(Color::Red));
}
#[test]
fn as_known_returns_none_for_unknown() {
let v: EnumValue<Color> = EnumValue::Unknown(7);
assert_eq!(v.as_known(), None);
}
#[test]
fn debug_known_variant_uses_enum_debug() {
let v = EnumValue::Known(Color::Blue);
assert_eq!(format!("{v:?}"), "Blue");
}
#[test]
fn debug_unknown_shows_raw_value() {
let v: EnumValue<Color> = EnumValue::Unknown(99);
assert_eq!(format!("{v:?}"), "Unknown(99)");
}
#[test]
fn display_known_variant_uses_proto_name() {
let v = EnumValue::Known(Color::Green);
assert_eq!(format!("{v}"), "GREEN");
}
#[test]
fn display_unknown_shows_raw_integer() {
let v: EnumValue<Color> = EnumValue::Unknown(99);
assert_eq!(format!("{v}"), "99");
}
#[test]
fn display_unknown_negative_shows_signed_integer() {
let v: EnumValue<Color> = EnumValue::Unknown(-1);
assert_eq!(format!("{v}"), "-1");
}
#[test]
fn default_produces_known_when_zero_is_valid() {
let v: EnumValue<Color> = EnumValue::default();
assert_eq!(v, EnumValue::Known(Color::Red));
}
#[test]
fn default_produces_unknown_when_zero_is_not_a_valid_variant() {
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum Status {
Active,
Inactive,
}
impl Enumeration for Status {
fn from_i32(v: i32) -> Option<Self> {
match v {
1 => Some(Status::Active),
2 => Some(Status::Inactive),
_ => None,
}
}
fn to_i32(&self) -> i32 {
match self {
Status::Active => 1,
Status::Inactive => 2,
}
}
fn proto_name(&self) -> &'static str {
match self {
Status::Active => "ACTIVE",
Status::Inactive => "INACTIVE",
}
}
}
let v: EnumValue<Status> = EnumValue::default();
assert_eq!(v, EnumValue::Unknown(0));
}
#[test]
fn is_known_returns_true_for_known_variant() {
let v = EnumValue::Known(Color::Red);
assert!(v.is_known());
assert!(!v.is_unknown());
}
#[test]
fn is_unknown_returns_true_for_unknown_value() {
let v: EnumValue<Color> = EnumValue::Unknown(99);
assert!(v.is_unknown());
assert!(!v.is_known());
}
#[test]
fn known_variant_equals_same_variant() {
let v = EnumValue::Known(Color::Green);
assert!(v == Color::Green);
}
#[test]
fn known_variant_not_equal_to_different_variant() {
let v = EnumValue::Known(Color::Red);
assert!(!(v == Color::Green));
}
#[test]
fn unknown_value_never_equals_known_variant() {
let v: EnumValue<Color> = EnumValue::Unknown(1); assert!(!(v == Color::Green));
}
#[test]
fn unknown_value_with_out_of_range_int_not_equal() {
let v: EnumValue<Color> = EnumValue::Unknown(99);
assert!(!(v == Color::Red));
}
#[cfg(feature = "json")]
mod serde_tests {
use super::*;
#[test]
fn known_variant_serializes_as_proto_name() {
let v = EnumValue::Known(Color::Green);
assert_eq!(serde_json::to_string(&v).unwrap(), r#""GREEN""#);
}
#[test]
fn unknown_variant_serializes_as_integer() {
let v: EnumValue<Color> = EnumValue::Unknown(99);
assert_eq!(serde_json::to_string(&v).unwrap(), "99");
}
#[test]
fn deserialize_table() {
use EnumValue::{Known, Unknown};
#[rustfmt::skip]
let cases: &[(&str, Option<EnumValue<Color>>)] = &[
(r#""RED""#, Some(Known(Color::Red))), ("1", Some(Known(Color::Green))), ("99", Some(Unknown(99))), ("null", Some(Known(Color::Red))), (r#""PURPLE""#, None), ("9223372036854775807", None), ];
for &(json, expected) in cases {
let result = serde_json::from_str::<EnumValue<Color>>(json);
assert_eq!(result.ok(), expected, "input: {json}");
}
}
#[test]
fn deserialize_unknown_string_returns_default_when_lenient() {
use crate::json::{with_json_parse_options, JsonParseOptions};
let opts = JsonParseOptions {
ignore_unknown_enum_values: true,
..Default::default()
};
let v: EnumValue<Color> =
with_json_parse_options(&opts, || serde_json::from_str(r#""PURPLE""#).unwrap());
assert_eq!(v, EnumValue::Known(Color::Red));
}
#[test]
fn deserialize_known_string_unaffected_by_lenient_mode() {
use crate::json::{with_json_parse_options, JsonParseOptions};
let opts = JsonParseOptions {
ignore_unknown_enum_values: true,
..Default::default()
};
let v: EnumValue<Color> =
with_json_parse_options(&opts, || serde_json::from_str(r#""BLUE""#).unwrap());
assert_eq!(v, EnumValue::Known(Color::Blue));
}
#[test]
fn round_trip_known_variant() {
let original = EnumValue::Known(Color::Blue);
let json = serde_json::to_string(&original).unwrap();
let recovered: EnumValue<Color> = serde_json::from_str(&json).unwrap();
assert_eq!(original, recovered);
}
}
}