1use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum Role {
12 Viewer,
15 Editor,
18 Admin,
21}
22
23impl Role {
24 pub const ALL: [Role; 3] = [Role::Viewer, Role::Editor, Role::Admin];
25
26 pub fn as_str(self) -> &'static str {
27 match self {
28 Role::Viewer => "viewer",
29 Role::Editor => "editor",
30 Role::Admin => "admin",
31 }
32 }
33
34 pub fn label(self) -> &'static str {
35 match self {
36 Role::Viewer => "Viewer",
37 Role::Editor => "CAD editor",
38 Role::Admin => "Admin",
39 }
40 }
41
42 pub fn parse(s: &str) -> Option<Role> {
45 match s
46 .trim()
47 .to_ascii_lowercase()
48 .replace([' ', '_'], "-")
49 .as_str()
50 {
51 "viewer" => Some(Role::Viewer),
52 "editor" | "cad-editor" | "engineer" => Some(Role::Editor),
53 "admin" => Some(Role::Admin),
54 _ => None,
55 }
56 }
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63pub enum AdminRead {
64 Implicit,
66 WithReason,
69 None,
72}
73
74impl AdminRead {
75 pub fn as_str(self) -> &'static str {
76 match self {
77 AdminRead::Implicit => "implicit",
78 AdminRead::WithReason => "with_reason",
79 AdminRead::None => "none",
80 }
81 }
82
83 pub fn label(self) -> &'static str {
84 match self {
85 AdminRead::Implicit => "Administrators read the group",
86 AdminRead::WithReason => "Administrators need a reason (time-limited, audited)",
87 AdminRead::None => "Administrators manage but cannot read",
88 }
89 }
90
91 pub fn parse(s: &str) -> Option<AdminRead> {
92 match s.trim().to_ascii_lowercase().as_str() {
93 "implicit" => Some(AdminRead::Implicit),
94 "with_reason" => Some(AdminRead::WithReason),
95 "none" => Some(AdminRead::None),
96 _ => None,
97 }
98 }
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
107#[serde(rename_all = "snake_case")]
108pub enum ProjectVisibility {
109 Internal,
111 Restricted,
114}
115
116impl ProjectVisibility {
117 pub fn as_str(self) -> &'static str {
118 match self {
119 ProjectVisibility::Internal => "internal",
120 ProjectVisibility::Restricted => "restricted",
121 }
122 }
123
124 pub fn label(self) -> &'static str {
125 match self {
126 ProjectVisibility::Internal => "Internal",
127 ProjectVisibility::Restricted => "Restricted",
128 }
129 }
130
131 pub fn parse(s: &str) -> Option<ProjectVisibility> {
132 match s.trim().to_ascii_lowercase().as_str() {
133 "internal" => Some(ProjectVisibility::Internal),
134 "restricted" => Some(ProjectVisibility::Restricted),
135 _ => None,
136 }
137 }
138}
139
140#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn roles() {
148 let roles: Vec<(&str, &str)> = Role::ALL.iter().map(|r| (r.as_str(), r.label())).collect();
149 assert_eq!(
150 roles,
151 [
152 ("viewer", "Viewer"),
153 ("editor", "CAD editor"),
154 ("admin", "Admin")
155 ]
156 );
157 for r in Role::ALL {
158 assert_eq!(Role::parse(r.as_str()), Some(r));
159 }
160 for alias in ["CAD editor", "cad_editor", "Engineer", " EDITOR "] {
161 assert_eq!(Role::parse(alias), Some(Role::Editor), "{alias}");
162 }
163 assert_eq!(Role::parse("owner"), None);
164 }
165
166 #[test]
167 fn admin_read_policies() {
168 let all = [AdminRead::Implicit, AdminRead::WithReason, AdminRead::None];
169 let stored: Vec<&str> = all.iter().map(|a| a.as_str()).collect();
170 assert_eq!(stored, ["implicit", "with_reason", "none"]);
171 for a in all {
172 assert_eq!(AdminRead::parse(a.as_str()), Some(a));
173 }
174 assert_eq!(AdminRead::parse(" NONE "), Some(AdminRead::None));
175 assert_eq!(AdminRead::parse("never"), None);
176 assert_eq!(AdminRead::Implicit.label(), "Administrators read the group");
177 assert_eq!(
178 AdminRead::WithReason.label(),
179 "Administrators need a reason (time-limited, audited)"
180 );
181 assert_eq!(
182 AdminRead::None.label(),
183 "Administrators manage but cannot read"
184 );
185 }
186
187 #[test]
188 fn visibilities() {
189 let all = [ProjectVisibility::Internal, ProjectVisibility::Restricted];
190 let v: Vec<(&str, &str)> = all.iter().map(|v| (v.as_str(), v.label())).collect();
191 assert_eq!(v, [("internal", "Internal"), ("restricted", "Restricted")]);
192 for v in all {
193 assert_eq!(ProjectVisibility::parse(v.as_str()), Some(v));
194 }
195 assert_eq!(ProjectVisibility::parse("secret"), None);
196 }
197}