Skip to main content

obs_types/
classification.rs

1//! [`Classification`] — security/PII classification of a field.
2
3use std::str::FromStr;
4
5use buffa::Enumeration;
6use serde::{Deserialize, Serialize};
7
8use crate::UnknownVariant;
9
10/// Security/PII classification of a field.
11///
12/// Drives lint L002 (PII never on LABEL) and L003 (SECRET never on LOG/AUDIT
13/// tier). See [70-security-and-classification.md](
14/// ../../specs/70-security-and-classification.md).
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17#[repr(i32)]
18#[non_exhaustive]
19pub enum Classification {
20    /// Never appears in a well-formed schema.
21    #[default]
22    Unspecified = 0,
23    /// Plain internal field; no special handling.
24    Internal = 1,
25    /// Personally identifiable information; redactable; never on LABEL.
26    Pii = 2,
27    /// Stripped before durable write; never on LOG/AUDIT tier.
28    Secret = 3,
29}
30
31impl Classification {
32    /// Stable string label.
33    #[must_use]
34    pub const fn as_str(self) -> &'static str {
35        match self {
36            Self::Unspecified => "unspecified",
37            Self::Internal => "internal",
38            Self::Pii => "pii",
39            Self::Secret => "secret",
40        }
41    }
42}
43
44impl Enumeration for Classification {
45    fn from_i32(value: i32) -> Option<Self> {
46        match value {
47            0 => Some(Self::Unspecified),
48            1 => Some(Self::Internal),
49            2 => Some(Self::Pii),
50            3 => Some(Self::Secret),
51            _ => None,
52        }
53    }
54
55    fn to_i32(&self) -> i32 {
56        *self as i32
57    }
58
59    fn proto_name(&self) -> &'static str {
60        match self {
61            Self::Unspecified => "CLASSIFICATION_UNSPECIFIED",
62            Self::Internal => "INTERNAL",
63            Self::Pii => "PII",
64            Self::Secret => "SECRET",
65        }
66    }
67
68    fn from_proto_name(name: &str) -> Option<Self> {
69        match name {
70            "CLASSIFICATION_UNSPECIFIED" => Some(Self::Unspecified),
71            "INTERNAL" => Some(Self::Internal),
72            "PII" => Some(Self::Pii),
73            "SECRET" => Some(Self::Secret),
74            _ => None,
75        }
76    }
77
78    fn values() -> &'static [Self] {
79        &[Self::Unspecified, Self::Internal, Self::Pii, Self::Secret]
80    }
81}
82
83impl FromStr for Classification {
84    type Err = UnknownVariant;
85
86    fn from_str(s: &str) -> Result<Self, Self::Err> {
87        match s.to_ascii_lowercase().as_str() {
88            "internal" => Ok(Self::Internal),
89            "pii" => Ok(Self::Pii),
90            "secret" => Ok(Self::Secret),
91            _ => Err(UnknownVariant {
92                kind: "Classification",
93                value: s.to_string(),
94            }),
95        }
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_should_round_trip_via_i32() {
105        for v in Classification::values() {
106            assert_eq!(Classification::from_i32(v.to_i32()), Some(*v));
107        }
108    }
109}