lodestone_types/
workload_class.rs1use std::error::Error;
2use std::fmt::{self, Display, Formatter};
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
10#[serde(rename_all = "snake_case")]
11pub enum WorkloadClassV1 {
12 InteractiveSearch,
13 BatchAnalytics,
14 StreamingIngest,
15 BulkBackfill,
16 InternalMaintenance,
17}
18
19impl WorkloadClassV1 {
20 pub const ALL: [Self; 5] = [
21 Self::InteractiveSearch,
22 Self::BatchAnalytics,
23 Self::StreamingIngest,
24 Self::BulkBackfill,
25 Self::InternalMaintenance,
26 ];
27
28 pub const PUBLIC: [Self; 4] = [
29 Self::InteractiveSearch,
30 Self::BatchAnalytics,
31 Self::StreamingIngest,
32 Self::BulkBackfill,
33 ];
34
35 #[must_use]
36 pub const fn is_public(self) -> bool {
37 !matches!(self, Self::InternalMaintenance)
38 }
39
40 const fn bit(self) -> u8 {
41 match self {
42 Self::InteractiveSearch => 1 << 0,
43 Self::BatchAnalytics => 1 << 1,
44 Self::StreamingIngest => 1 << 2,
45 Self::BulkBackfill => 1 << 3,
46 Self::InternalMaintenance => 1 << 4,
47 }
48 }
49}
50
51#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
55pub struct WorkloadClassCapabilitiesV1(u8);
56
57impl WorkloadClassCapabilitiesV1 {
58 #[must_use]
59 pub const fn empty() -> Self {
60 Self(0)
61 }
62
63 pub fn from_public_classes(
64 classes: impl IntoIterator<Item = WorkloadClassV1>,
65 ) -> Result<Self, WorkloadClassCapabilityError> {
66 let mut value = Self::empty();
67 for class in classes {
68 if !class.is_public() {
69 return Err(WorkloadClassCapabilityError::InternalClassAtPublicBoundary);
70 }
71 value.0 |= class.bit();
72 }
73 if value.is_empty() {
74 return Err(WorkloadClassCapabilityError::Empty);
75 }
76 Ok(value)
77 }
78
79 #[must_use]
80 pub const fn allows(self, class: WorkloadClassV1) -> bool {
81 self.0 & class.bit() != 0
82 }
83
84 #[must_use]
85 pub const fn is_empty(self) -> bool {
86 self.0 == 0
87 }
88}
89
90#[derive(Clone, Copy, Debug, Eq, PartialEq)]
91pub enum WorkloadClassCapabilityError {
92 Empty,
93 InternalClassAtPublicBoundary,
94}
95
96impl Display for WorkloadClassCapabilityError {
97 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
98 match self {
99 Self::Empty => formatter.write_str("workload-class capability set is empty"),
100 Self::InternalClassAtPublicBoundary => {
101 formatter.write_str("internal-maintenance class is not a public capability")
102 }
103 }
104 }
105}
106
107impl Error for WorkloadClassCapabilityError {}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn public_capabilities_are_exact_and_internal_is_refused() -> Result<(), Box<dyn Error>> {
115 let capabilities = WorkloadClassCapabilitiesV1::from_public_classes([
116 WorkloadClassV1::InteractiveSearch,
117 WorkloadClassV1::BulkBackfill,
118 ])?;
119 assert!(capabilities.allows(WorkloadClassV1::InteractiveSearch));
120 assert!(capabilities.allows(WorkloadClassV1::BulkBackfill));
121 assert!(!capabilities.allows(WorkloadClassV1::BatchAnalytics));
122 assert!(!capabilities.allows(WorkloadClassV1::InternalMaintenance));
123 assert_eq!(
124 WorkloadClassCapabilitiesV1::from_public_classes([WorkloadClassV1::InternalMaintenance]),
125 Err(WorkloadClassCapabilityError::InternalClassAtPublicBoundary)
126 );
127 Ok(())
128 }
129}