1use std::cmp::Ordering;
4use std::fmt::Display;
5
6#[cfg(any(test, feature = "serde"))]
7use serde::{Deserialize, Serialize};
8
9use crate::traits::Conditions;
10
11#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
14#[cfg_attr(any(test, feature = "serde"), derive(Deserialize, Serialize))]
15pub enum AccessLevel {
16 Pull,
18
19 Read,
21
22 Write,
24
25 Manage,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
39#[cfg_attr(any(test, feature = "serde"), derive(Deserialize, Serialize))]
40pub struct Access<C = ()> {
41 pub conditions: Option<C>,
42 pub level: AccessLevel,
43}
44
45impl<C> Display for Access<C> {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 let s = match self.level {
48 AccessLevel::Pull => "pull",
49 AccessLevel::Read => "read",
50 AccessLevel::Write => "write",
51 AccessLevel::Manage => "manage",
52 };
53
54 write!(f, "{s}")
55 }
56}
57
58impl<C> Access<C> {
59 pub fn pull() -> Self {
61 Self {
62 level: AccessLevel::Pull,
63 conditions: None,
64 }
65 }
66
67 pub fn read() -> Self {
69 Self {
70 level: AccessLevel::Read,
71 conditions: None,
72 }
73 }
74
75 pub fn write() -> Self {
77 Self {
78 level: AccessLevel::Write,
79 conditions: None,
80 }
81 }
82
83 pub fn manage() -> Self {
85 Self {
86 level: AccessLevel::Manage,
87 conditions: None,
88 }
89 }
90
91 pub fn with_conditions(mut self, conditions: C) -> Self {
93 self.conditions = Some(conditions);
94 self
95 }
96
97 pub fn is_pull(&self) -> bool {
99 matches!(self.level, AccessLevel::Pull)
100 }
101
102 pub fn is_read(&self) -> bool {
104 matches!(self.level, AccessLevel::Read)
105 }
106
107 pub fn is_write(&self) -> bool {
109 matches!(self.level, AccessLevel::Write)
110 }
111
112 pub fn is_manage(&self) -> bool {
114 matches!(self.level, AccessLevel::Manage)
115 }
116}
117
118impl<C: PartialOrd> PartialOrd for Access<C> {
119 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
120 match (self.conditions.as_ref(), other.conditions.as_ref()) {
121 (Some(self_cond), Some(other_cond)) => {
123 match self_cond.partial_cmp(other_cond) {
124 Some(Ordering::Greater | Ordering::Equal) => {
127 match self.level.cmp(&other.level) {
128 Ordering::Less => Some(Ordering::Less),
129 Ordering::Equal | Ordering::Greater => Some(Ordering::Greater),
130 }
131 }
132 Some(Ordering::Less) => Some(Ordering::Less),
133 None => None,
134 }
135 }
136 (None, Some(_)) => match self.level.cmp(&other.level) {
137 Ordering::Less => Some(Ordering::Less),
138 Ordering::Equal | Ordering::Greater => Some(Ordering::Greater),
139 },
140 _ => Some(self.level.cmp(&other.level)),
141 }
142 }
143}
144
145impl<C: PartialOrd + Eq> Ord for Access<C> {
146 fn cmp(&self, other: &Self) -> Ordering {
147 self.partial_cmp(other).unwrap_or(Ordering::Less)
148 }
149}
150
151impl Conditions for () {}
152
153#[cfg(test)]
154mod tests {
155 use std::cmp::Ordering;
156
157 use crate::Access;
158
159 #[derive(Debug, Clone, PartialEq, Eq)]
162 struct PathCondition(String);
163
164 impl PartialOrd for PathCondition {
165 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
166 let self_parts: Vec<_> = self.0.split('/').filter(|s| !s.is_empty()).collect();
167 let other_parts: Vec<_> = other.0.split('/').filter(|s| !s.is_empty()).collect();
168
169 let min_len = self_parts.len().min(other_parts.len());
170 let is_prefix = self_parts[..min_len] == other_parts[..min_len];
171
172 if is_prefix {
173 match self_parts.len().cmp(&other_parts.len()) {
174 Ordering::Less => Some(Ordering::Greater),
175 Ordering::Equal => Some(Ordering::Equal),
176 Ordering::Greater => Some(Ordering::Less),
177 }
178 } else {
179 None
180 }
181 }
182 }
183
184 #[test]
185 fn path_condition_comparators() {
186 let root_access = Access::read().with_conditions(PathCondition("/root".to_string()));
187 let private_access =
188 Access::read().with_conditions(PathCondition("/root/private".to_string()));
189 let public_access =
190 Access::read().with_conditions(PathCondition("/root/public".to_string()));
191
192 assert!(root_access >= private_access);
194 assert!(root_access >= public_access);
195
196 assert!(!(private_access >= public_access));
198 assert!(!(private_access <= public_access));
199
200 let read_access_to_root =
201 Access::read().with_conditions(PathCondition("/root".to_string()));
202 let requested_write_access_to_sub_path =
203 Access::write().with_conditions(PathCondition("/root/private".to_string()));
204
205 assert!(requested_write_access_to_sub_path < read_access_to_root);
206
207 let unconditional_read = Access::<PathCondition>::read();
208 assert!(unconditional_read > public_access);
209 }
210
211 #[derive(Debug, Clone, PartialOrd, PartialEq, Eq)]
213 struct ExpiryTimestamp(u64);
214
215 #[test]
216 fn expiry_timestamp_access_ordering() {
217 let access_expires_soon = Access::read().with_conditions(ExpiryTimestamp(10));
218 let access_expires_later = Access::read().with_conditions(ExpiryTimestamp(100));
219
220 assert!(access_expires_later > access_expires_soon);
222
223 assert!(access_expires_soon < access_expires_later);
225
226 const NOW: ExpiryTimestamp = ExpiryTimestamp(50);
230 let requested_read_access = Access::read().with_conditions(NOW);
231
232 assert!(access_expires_soon < requested_read_access);
235
236 assert!(access_expires_later >= requested_read_access);
239
240 let requested_pull_access = Access::pull().with_conditions(NOW);
244 assert!(access_expires_soon < requested_pull_access);
245
246 let requested_write_access = Access::write().with_conditions(NOW);
249 assert!(access_expires_later < requested_write_access);
250
251 let requested_read_access = Access::read().with_conditions(NOW);
253 let access_no_expiry = Access::<ExpiryTimestamp>::read();
254 assert!(access_no_expiry > requested_read_access);
255 }
256}