Skip to main content

gbp_core/
conformance.rs

1//! Interoperability conformance classes (gbp-interop-profile §2).
2//!
3//! Class A = GBP + GSP (mandatory base)
4//! Class B = Class A + GTP
5//! Class C = Class B + GAP (full stack)
6//!
7//! Implementations MUST declare their conformance class during capability
8//! negotiation using the well-known token strings defined here.
9
10use core::fmt;
11
12/// Interoperability conformance class (gbp-interop-profile §2).
13#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
14pub enum ConformanceClass {
15    /// Class A: GBP + GSP (mandatory minimum).
16    A,
17    /// Class B: Class A + GTP (text messaging).
18    B,
19    /// Class C: Class B + GAP (audio).
20    C,
21}
22
23impl ConformanceClass {
24    /// Well-known capability token for Class A.
25    pub const TOKEN_A: &'static str = "gbp/class-a";
26    /// Well-known capability token for Class B.
27    pub const TOKEN_B: &'static str = "gbp/class-b";
28    /// Well-known capability token for Class C.
29    pub const TOKEN_C: &'static str = "gbp/class-c";
30
31    /// Returns all capability tokens that must be advertised for this class
32    /// (including tokens for lower classes — each class implies the ones below).
33    pub fn tokens(self) -> &'static [&'static str] {
34        match self {
35            Self::A => &[Self::TOKEN_A],
36            Self::B => &[Self::TOKEN_A, Self::TOKEN_B],
37            Self::C => &[Self::TOKEN_A, Self::TOKEN_B, Self::TOKEN_C],
38        }
39    }
40
41    /// Infers the highest conformance class from a set of capability tokens.
42    /// Returns `None` if not even Class A is present.
43    pub fn from_tokens<'a>(tokens: impl IntoIterator<Item = &'a str>) -> Option<Self> {
44        let mut has_a = false;
45        let mut has_b = false;
46        let mut has_c = false;
47        for t in tokens {
48            match t {
49                Self::TOKEN_A => has_a = true,
50                Self::TOKEN_B => has_b = true,
51                Self::TOKEN_C => has_c = true,
52                _ => {}
53            }
54        }
55        if has_a && has_b && has_c {
56            Some(Self::C)
57        } else if has_a && has_b {
58            Some(Self::B)
59        } else if has_a {
60            Some(Self::A)
61        } else {
62            None
63        }
64    }
65}
66
67impl fmt::Display for ConformanceClass {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        f.write_str(match self {
70            Self::A => "Class A (GBP+GSP)",
71            Self::B => "Class B (GBP+GSP+GTP)",
72            Self::C => "Class C (GBP+GSP+GTP+GAP)",
73        })
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn tokens_are_cumulative() {
83        assert_eq!(ConformanceClass::A.tokens(), &["gbp/class-a"]);
84        assert_eq!(
85            ConformanceClass::B.tokens(),
86            &["gbp/class-a", "gbp/class-b"]
87        );
88        assert_eq!(
89            ConformanceClass::C.tokens(),
90            &["gbp/class-a", "gbp/class-b", "gbp/class-c"]
91        );
92    }
93
94    #[test]
95    fn from_tokens_detects_class() {
96        assert_eq!(
97            ConformanceClass::from_tokens(["gbp/class-a"]),
98            Some(ConformanceClass::A)
99        );
100        assert_eq!(
101            ConformanceClass::from_tokens(["gbp/class-a", "gbp/class-b"]),
102            Some(ConformanceClass::B)
103        );
104        assert_eq!(
105            ConformanceClass::from_tokens(["gbp/class-a", "gbp/class-b", "gbp/class-c"]),
106            Some(ConformanceClass::C)
107        );
108        assert_eq!(ConformanceClass::from_tokens(["something-else"]), None);
109    }
110
111    #[test]
112    fn class_ordering() {
113        assert!(ConformanceClass::A < ConformanceClass::B);
114        assert!(ConformanceClass::B < ConformanceClass::C);
115    }
116}