1#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8#[repr(u8)]
9pub enum ProfileId {
10 Generic = 0,
12 Core = 1,
14 Hot = 2,
16 Full = 3,
18}
19
20impl TryFrom<u8> for ProfileId {
21 type Error = u8;
22
23 fn try_from(value: u8) -> Result<Self, Self::Error> {
24 match value {
25 0 => Ok(Self::Generic),
26 1 => Ok(Self::Core),
27 2 => Ok(Self::Hot),
28 3 => Ok(Self::Full),
29 other => Err(other),
30 }
31 }
32}
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39#[repr(u8)]
40pub enum DomainProfile {
41 Generic = 0,
43 Rvdna = 1,
45 RvText = 2,
47 RvGraph = 3,
49 RvVision = 4,
51}
52
53impl DomainProfile {
54 pub const fn magic(self) -> u32 {
56 match self {
57 Self::Generic => 0x0000_0000,
58 Self::Rvdna => 0x5244_4E41, Self::RvText => 0x5254_5854, Self::RvGraph => 0x5247_5248, Self::RvVision => 0x5256_4953, }
63 }
64}
65
66impl DomainProfile {
67 pub const fn extension(&self) -> &'static str {
69 match self {
70 Self::Generic => "rvf",
71 Self::Rvdna => "rvdna",
72 Self::RvText => "rvtext",
73 Self::RvGraph => "rvgraph",
74 Self::RvVision => "rvvis",
75 }
76 }
77
78 pub fn from_extension(ext: &str) -> Option<Self> {
80 let ext_bytes = ext.as_bytes();
82 if eq_ignore_ascii_case(ext_bytes, b"rvf") {
83 Some(Self::Generic)
84 } else if eq_ignore_ascii_case(ext_bytes, b"rvdna") {
85 Some(Self::Rvdna)
86 } else if eq_ignore_ascii_case(ext_bytes, b"rvtext") {
87 Some(Self::RvText)
88 } else if eq_ignore_ascii_case(ext_bytes, b"rvgraph") {
89 Some(Self::RvGraph)
90 } else if eq_ignore_ascii_case(ext_bytes, b"rvvis") {
91 Some(Self::RvVision)
92 } else {
93 None
94 }
95 }
96}
97
98fn eq_ignore_ascii_case(a: &[u8], b: &[u8]) -> bool {
100 if a.len() != b.len() {
101 return false;
102 }
103 a.iter().zip(b.iter()).all(|(x, y)| x.eq_ignore_ascii_case(y))
104}
105
106impl TryFrom<u8> for DomainProfile {
107 type Error = u8;
108
109 fn try_from(value: u8) -> Result<Self, Self::Error> {
110 match value {
111 0 => Ok(Self::Generic),
112 1 => Ok(Self::Rvdna),
113 2 => Ok(Self::RvText),
114 3 => Ok(Self::RvGraph),
115 4 => Ok(Self::RvVision),
116 other => Err(other),
117 }
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn profile_id_round_trip() {
127 for raw in 0..=3u8 {
128 let p = ProfileId::try_from(raw).unwrap();
129 assert_eq!(p as u8, raw);
130 }
131 assert_eq!(ProfileId::try_from(4), Err(4));
132 }
133
134 #[test]
135 fn domain_profile_round_trip() {
136 for raw in 0..=4u8 {
137 let d = DomainProfile::try_from(raw).unwrap();
138 assert_eq!(d as u8, raw);
139 }
140 assert_eq!(DomainProfile::try_from(5), Err(5));
141 }
142
143 #[test]
144 fn domain_extension_round_trip() {
145 let profiles = [
146 DomainProfile::Generic,
147 DomainProfile::Rvdna,
148 DomainProfile::RvText,
149 DomainProfile::RvGraph,
150 DomainProfile::RvVision,
151 ];
152 for p in profiles {
153 let ext = p.extension();
154 let back = DomainProfile::from_extension(ext).unwrap();
155 assert_eq!(back, p, "round-trip failed for {ext}");
156 }
157 }
158
159 #[test]
160 fn domain_extension_case_insensitive() {
161 assert_eq!(DomainProfile::from_extension("RVDNA"), Some(DomainProfile::Rvdna));
162 assert_eq!(DomainProfile::from_extension("RvF"), Some(DomainProfile::Generic));
163 assert_eq!(DomainProfile::from_extension("RvText"), Some(DomainProfile::RvText));
164 }
165
166 #[test]
167 fn domain_extension_unknown() {
168 assert_eq!(DomainProfile::from_extension("txt"), None);
169 assert_eq!(DomainProfile::from_extension(""), None);
170 }
171
172 #[test]
173 fn domain_magic_values() {
174 assert_eq!(&DomainProfile::Rvdna.magic().to_be_bytes(), b"RDNA");
175 assert_eq!(&DomainProfile::RvText.magic().to_be_bytes(), b"RTXT");
176 assert_eq!(&DomainProfile::RvGraph.magic().to_be_bytes(), b"RGRH");
177 assert_eq!(&DomainProfile::RvVision.magic().to_be_bytes(), b"RVIS");
178 }
179}