1use std::str::FromStr;
4
5use buffa::Enumeration;
6use serde::{Deserialize, Serialize};
7
8use crate::UnknownVariant;
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17#[repr(i32)]
18#[non_exhaustive]
19pub enum Cardinality {
20 #[default]
22 Unspecified = 0,
23 Low = 1,
25 Medium = 2,
27 High = 3,
29 Unbounded = 4,
31}
32
33impl Cardinality {
34 #[must_use]
36 pub const fn as_str(self) -> &'static str {
37 match self {
38 Self::Unspecified => "unspecified",
39 Self::Low => "low",
40 Self::Medium => "medium",
41 Self::High => "high",
42 Self::Unbounded => "unbounded",
43 }
44 }
45
46 #[must_use]
52 pub const fn cap(self) -> u64 {
53 match self {
54 Self::Unspecified => 0,
55 Self::Low => 10,
56 Self::Medium => 10_000,
57 Self::High => 1_000_000,
58 Self::Unbounded => u64::MAX,
59 }
60 }
61
62 #[must_use]
65 pub const fn is_label_compatible(self) -> bool {
66 matches!(self, Self::Low | Self::Medium)
67 }
68
69 #[must_use]
73 pub const fn is_measurement_compatible(self) -> bool {
74 !matches!(self, Self::Unbounded)
75 }
76}
77
78impl Enumeration for Cardinality {
79 fn from_i32(value: i32) -> Option<Self> {
80 match value {
81 0 => Some(Self::Unspecified),
82 1 => Some(Self::Low),
83 2 => Some(Self::Medium),
84 3 => Some(Self::High),
85 4 => Some(Self::Unbounded),
86 _ => None,
87 }
88 }
89
90 fn to_i32(&self) -> i32 {
91 *self as i32
92 }
93
94 fn proto_name(&self) -> &'static str {
95 match self {
96 Self::Unspecified => "CARDINALITY_UNSPECIFIED",
97 Self::Low => "LOW",
98 Self::Medium => "MEDIUM",
99 Self::High => "HIGH",
100 Self::Unbounded => "UNBOUNDED",
101 }
102 }
103
104 fn from_proto_name(name: &str) -> Option<Self> {
105 match name {
106 "CARDINALITY_UNSPECIFIED" => Some(Self::Unspecified),
107 "LOW" => Some(Self::Low),
108 "MEDIUM" => Some(Self::Medium),
109 "HIGH" => Some(Self::High),
110 "UNBOUNDED" => Some(Self::Unbounded),
111 _ => None,
112 }
113 }
114
115 fn values() -> &'static [Self] {
116 &[
117 Self::Unspecified,
118 Self::Low,
119 Self::Medium,
120 Self::High,
121 Self::Unbounded,
122 ]
123 }
124}
125
126impl FromStr for Cardinality {
127 type Err = UnknownVariant;
128
129 fn from_str(s: &str) -> Result<Self, Self::Err> {
130 match s.to_ascii_lowercase().as_str() {
131 "low" => Ok(Self::Low),
132 "medium" => Ok(Self::Medium),
133 "high" => Ok(Self::High),
134 "unbounded" => Ok(Self::Unbounded),
135 _ => Err(UnknownVariant {
136 kind: "Cardinality",
137 value: s.to_string(),
138 }),
139 }
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_should_round_trip_via_i32() {
149 for v in Cardinality::values() {
150 assert_eq!(Cardinality::from_i32(v.to_i32()), Some(*v));
151 }
152 }
153
154 #[test]
155 fn test_should_enforce_label_compatibility() {
156 assert!(Cardinality::Low.is_label_compatible());
157 assert!(Cardinality::Medium.is_label_compatible());
158 assert!(!Cardinality::High.is_label_compatible());
159 assert!(!Cardinality::Unbounded.is_label_compatible());
160 }
161
162 #[test]
163 fn test_should_compute_caps() {
164 assert_eq!(Cardinality::Low.cap(), 10);
165 assert_eq!(Cardinality::Medium.cap(), 10_000);
166 assert_eq!(Cardinality::High.cap(), 1_000_000);
167 assert_eq!(Cardinality::Unbounded.cap(), u64::MAX);
168 }
169}