1use std::{cmp::Ordering, error::Error, fmt};
2
3use steel_utils::Identifier;
4
5use super::{PermissionKeyError, PermissionSegment};
6
7#[derive(Clone, Debug, PartialEq, Eq, Hash)]
9pub enum PermissionRuleContext {
10 Global,
12 Domain(PermissionDomain),
14 World(Identifier),
16 Custom {
18 key: PermissionContextKey,
20 value: PermissionContextValue,
22 },
23 All(PermissionRuleContexts),
25}
26
27#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct PermissionDomain(String);
30
31impl PermissionDomain {
32 pub fn parse(value: impl Into<String>) -> Result<Self, PermissionRuleContextError> {
38 let value = value.into();
39 if value.is_empty() || !Identifier::validate_namespace(&value) {
40 return Err(PermissionRuleContextError::InvalidDomain(value));
41 }
42 Ok(Self(value))
43 }
44
45 #[must_use]
47 pub fn as_str(&self) -> &str {
48 &self.0
49 }
50}
51
52impl fmt::Display for PermissionDomain {
53 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
54 formatter.write_str(self.as_str())
55 }
56}
57
58#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
60pub struct PermissionContextValue(String);
61
62impl PermissionContextValue {
63 pub fn parse(value: impl Into<String>) -> Result<Self, PermissionRuleContextError> {
69 let value = value.into();
70 if value.is_empty()
71 || value
72 .chars()
73 .any(|character| character.is_whitespace() || "{},=".contains(character))
74 {
75 return Err(PermissionRuleContextError::InvalidValue(value));
76 }
77 Ok(Self(value))
78 }
79
80 #[must_use]
82 pub fn as_str(&self) -> &str {
83 &self.0
84 }
85}
86
87impl fmt::Display for PermissionContextValue {
88 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
89 formatter.write_str(self.as_str())
90 }
91}
92
93#[derive(Clone, Debug, PartialEq, Eq, Hash)]
95pub struct PermissionRuleContexts {
96 contexts: Vec<PermissionRuleContext>,
97}
98
99impl PermissionRuleContexts {
100 const fn new(contexts: Vec<PermissionRuleContext>) -> Self {
101 Self { contexts }
102 }
103
104 fn into_vec(self) -> Vec<PermissionRuleContext> {
105 self.contexts
106 }
107
108 pub fn iter(&self) -> impl Iterator<Item = &PermissionRuleContext> {
110 self.contexts.iter()
111 }
112}
113
114impl PermissionRuleContext {
115 #[must_use]
117 pub const fn global() -> Self {
118 Self::Global
119 }
120
121 pub fn domain(domain: impl Into<String>) -> Result<Self, PermissionRuleContextError> {
123 PermissionDomain::parse(domain).map(Self::Domain)
124 }
125
126 #[must_use]
128 pub const fn world(world: Identifier) -> Self {
129 Self::World(world)
130 }
131
132 pub fn custom(
138 key: PermissionContextKey,
139 value: impl Into<String>,
140 ) -> Result<Self, PermissionRuleContextError> {
141 let value = PermissionContextValue::parse(value)?;
142 Ok(Self::Custom { key, value })
143 }
144
145 pub fn all(
154 contexts: impl IntoIterator<Item = Self>,
155 ) -> Result<Self, PermissionRuleContextError> {
156 let mut flattened = Vec::new();
157 for context in contexts {
158 match context {
159 Self::Global => {}
160 Self::All(contexts) => {
161 for context in contexts.into_vec() {
162 push_unique_context(&mut flattened, context)?;
163 }
164 }
165 context => push_unique_context(&mut flattened, context)?,
166 }
167 }
168 normalize_world_domain(&mut flattened)?;
169 flattened.sort_by(compare_rule_contexts);
170
171 Ok(match flattened.len() {
172 0 => Self::Global,
173 1 => match flattened.pop() {
174 Some(context) => context,
175 None => Self::Global,
176 },
177 _ => Self::All(PermissionRuleContexts::new(flattened)),
178 })
179 }
180
181 pub(super) fn matches(&self, context: &PermissionContext) -> bool {
182 match self {
183 Self::Global => true,
184 Self::Domain(domain) => context.domain.as_ref() == Some(domain),
185 Self::World(world) => context.world.as_ref() == Some(world),
186 Self::Custom { .. } => context.custom_contexts.contains(self),
187 Self::All(contexts) => contexts
188 .iter()
189 .all(|constraint| constraint.matches(context)),
190 }
191 }
192
193 pub(super) fn specificity(&self) -> usize {
194 match self {
195 Self::Global => 0,
196 Self::Domain(_) | Self::Custom { .. } => 1,
197 Self::World(_) => 2,
198 Self::All(contexts) => contexts.iter().map(Self::specificity).sum(),
199 }
200 }
201}
202
203impl fmt::Display for PermissionRuleContext {
204 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match self {
206 Self::Global => formatter.write_str("global"),
207 Self::Domain(domain) => write!(formatter, "domain {}", domain.as_str()),
208 Self::World(world) => write!(formatter, "world {world}"),
209 Self::Custom { key, value } => {
210 write!(formatter, "{} {}", key.as_str(), value.as_str())
211 }
212 Self::All(contexts) => {
213 for (index, context) in contexts.iter().enumerate() {
214 if index != 0 {
215 formatter.write_str(" + ")?;
216 }
217 write!(formatter, "{context}")?;
218 }
219 Ok(())
220 }
221 }
222 }
223}
224
225fn normalize_world_domain(
226 contexts: &mut Vec<PermissionRuleContext>,
227) -> Result<(), PermissionRuleContextError> {
228 let domain = contexts.iter().find_map(|context| match context {
229 PermissionRuleContext::Domain(domain) => Some(domain.as_str()),
230 _ => None,
231 });
232 let world_domain = contexts.iter().find_map(|context| match context {
233 PermissionRuleContext::World(world) => Some(world.namespace.as_ref()),
234 _ => None,
235 });
236 let (Some(domain), Some(world_domain)) = (domain, world_domain) else {
237 return Ok(());
238 };
239 if domain != world_domain {
240 return Err(PermissionRuleContextError::WorldDomainMismatch {
241 domain: domain.to_owned(),
242 world_domain: world_domain.to_owned(),
243 });
244 }
245 contexts.retain(|context| !matches!(context, PermissionRuleContext::Domain(_)));
246 Ok(())
247}
248
249fn push_unique_context(
250 contexts: &mut Vec<PermissionRuleContext>,
251 context: PermissionRuleContext,
252) -> Result<(), PermissionRuleContextError> {
253 for existing in contexts.iter() {
254 match (&context, existing) {
255 (PermissionRuleContext::Domain(value), PermissionRuleContext::Domain(current)) => {
256 if value == current {
257 return Ok(());
258 }
259 return Err(PermissionRuleContextError::DuplicateDomain);
260 }
261 (PermissionRuleContext::World(value), PermissionRuleContext::World(current)) => {
262 if value == current {
263 return Ok(());
264 }
265 return Err(PermissionRuleContextError::DuplicateWorld);
266 }
267 (
268 PermissionRuleContext::Custom { key, value },
269 PermissionRuleContext::Custom {
270 key: current_key,
271 value: current_value,
272 },
273 ) if key == current_key => {
274 if value == current_value {
275 return Ok(());
276 }
277 return Err(PermissionRuleContextError::DuplicateCustomKey(key.clone()));
278 }
279 _ => {}
280 }
281 }
282 if !contexts.contains(&context) {
283 contexts.push(context);
284 }
285 Ok(())
286}
287
288fn compare_rule_contexts(left: &PermissionRuleContext, right: &PermissionRuleContext) -> Ordering {
289 rule_context_rank(left)
290 .cmp(&rule_context_rank(right))
291 .then_with(|| match (left, right) {
292 (PermissionRuleContext::Domain(left), PermissionRuleContext::Domain(right)) => {
293 left.cmp(right)
294 }
295 (PermissionRuleContext::World(left), PermissionRuleContext::World(right)) => left
296 .namespace
297 .cmp(&right.namespace)
298 .then_with(|| left.path.cmp(&right.path)),
299 (
300 PermissionRuleContext::Custom {
301 key: left_key,
302 value: left_value,
303 },
304 PermissionRuleContext::Custom {
305 key: right_key,
306 value: right_value,
307 },
308 ) => left_key
309 .as_str()
310 .cmp(right_key.as_str())
311 .then_with(|| left_value.cmp(right_value)),
312 _ => Ordering::Equal,
313 })
314}
315
316const fn rule_context_rank(context: &PermissionRuleContext) -> u8 {
317 match context {
318 PermissionRuleContext::Domain(_) => 0,
319 PermissionRuleContext::World(_) => 1,
320 PermissionRuleContext::Custom { .. } => 2,
321 PermissionRuleContext::Global => 3,
322 PermissionRuleContext::All(_) => 4,
323 }
324}
325
326#[derive(Clone, Debug, Default, PartialEq, Eq)]
328pub struct PermissionContext {
329 domain: Option<PermissionDomain>,
330 world: Option<Identifier>,
331 custom_contexts: Vec<PermissionRuleContext>,
332}
333
334impl PermissionContext {
335 #[must_use]
337 pub fn global() -> Self {
338 Self::default()
339 }
340
341 #[must_use]
343 pub fn for_world(world: Identifier) -> Self {
344 Self {
345 domain: Some(PermissionDomain(world.namespace.to_string())),
346 world: Some(world),
347 custom_contexts: Vec::new(),
348 }
349 }
350
351 pub fn from_rule_context(
359 rule_context: &PermissionRuleContext,
360 ) -> Result<Self, PermissionRuleContextError> {
361 let mut context = Self::global();
362 context.append_rule_context(rule_context)?;
363 Ok(context)
364 }
365
366 fn append_rule_context(
367 &mut self,
368 rule_context: &PermissionRuleContext,
369 ) -> Result<(), PermissionRuleContextError> {
370 match rule_context {
371 PermissionRuleContext::Global => {}
372 PermissionRuleContext::Domain(domain) => {
373 self.domain = Some(domain.clone());
374 }
375 PermissionRuleContext::World(world) => {
376 self.domain = Some(PermissionDomain(world.namespace.to_string()));
377 self.world = Some(world.clone());
378 }
379 PermissionRuleContext::Custom { key, value } => {
380 self.add_custom_context(key.clone(), value.as_str())?;
381 }
382 PermissionRuleContext::All(contexts) => {
383 for context in contexts.iter() {
384 self.append_rule_context(context)?;
385 }
386 }
387 }
388 Ok(())
389 }
390
391 pub fn with_custom_context(
397 mut self,
398 key: PermissionContextKey,
399 value: impl Into<String>,
400 ) -> Result<Self, PermissionRuleContextError> {
401 self.add_custom_context(key, value)?;
402 Ok(self)
403 }
404
405 pub fn add_custom_context(
411 &mut self,
412 key: PermissionContextKey,
413 value: impl Into<String>,
414 ) -> Result<(), PermissionRuleContextError> {
415 let context = PermissionRuleContext::custom(key, value)?;
416 let PermissionRuleContext::Custom { key, value } = &context else {
417 return Ok(());
418 };
419 for existing in &self.custom_contexts {
420 let PermissionRuleContext::Custom {
421 key: existing_key,
422 value: existing_value,
423 } = existing
424 else {
425 continue;
426 };
427 if existing_key != key {
428 continue;
429 }
430 if existing_value == value {
431 return Ok(());
432 }
433 return Err(PermissionRuleContextError::DuplicateCustomKey(key.clone()));
434 }
435 self.custom_contexts.push(context);
436 Ok(())
437 }
438}
439
440#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
442pub struct PermissionContextKey(String);
443
444impl PermissionContextKey {
445 pub fn parse(value: impl Into<String>) -> Result<Self, PermissionContextKeyError> {
451 let value = value.into();
452 if value.contains(':') {
453 validate_namespaced_context_key(&value)?;
454 return Ok(Self(value));
455 }
456 PermissionSegment::parse(value.clone())
457 .map_err(PermissionContextKeyError::InvalidUnqualified)?;
458 Ok(Self(value))
459 }
460
461 #[must_use]
463 pub fn as_str(&self) -> &str {
464 &self.0
465 }
466}
467
468fn validate_namespaced_context_key(value: &str) -> Result<(), PermissionContextKeyError> {
469 let Some((namespace, path)) = value.split_once(':') else {
470 return Err(PermissionContextKeyError::InvalidFormat);
471 };
472 if namespace.is_empty() {
473 return Err(PermissionContextKeyError::EmptyNamespace);
474 }
475 if path.is_empty() {
476 return Err(PermissionContextKeyError::EmptyPath);
477 }
478 if path.contains(':') {
479 return Err(PermissionContextKeyError::InvalidFormat);
480 }
481 if namespace.split('.').any(str::is_empty) {
482 return Err(PermissionContextKeyError::InvalidNamespace);
483 }
484 if path.split(['.', '/']).any(str::is_empty) {
485 return Err(PermissionContextKeyError::InvalidPath);
486 }
487 if !Identifier::validate_namespace(namespace) {
488 return Err(PermissionContextKeyError::InvalidNamespace);
489 }
490 if !Identifier::validate_path(path) {
491 return Err(PermissionContextKeyError::InvalidPath);
492 }
493 Ok(())
494}
495
496#[derive(Clone, Debug, PartialEq, Eq)]
498pub enum PermissionRuleContextError {
499 InvalidDomain(String),
501 InvalidValue(String),
503 DuplicateDomain,
505 DuplicateWorld,
507 DuplicateCustomKey(PermissionContextKey),
509 WorldDomainMismatch {
511 domain: String,
513 world_domain: String,
515 },
516}
517
518impl fmt::Display for PermissionRuleContextError {
519 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
520 match self {
521 Self::InvalidDomain(domain) => {
522 write!(formatter, "invalid permission context domain '{domain}'")
523 }
524 Self::InvalidValue(value) => {
525 write!(formatter, "invalid permission context value '{value}'")
526 }
527 Self::DuplicateDomain => {
528 formatter.write_str("domain permission context cannot have multiple values")
529 }
530 Self::DuplicateWorld => {
531 formatter.write_str("world permission context cannot have multiple values")
532 }
533 Self::DuplicateCustomKey(key) => write!(
534 formatter,
535 "custom permission context key '{}' cannot have multiple values",
536 key.as_str()
537 ),
538 Self::WorldDomainMismatch {
539 domain,
540 world_domain,
541 } => write!(
542 formatter,
543 "permission context domain '{domain}' conflicts with world domain '{world_domain}'"
544 ),
545 }
546 }
547}
548
549impl Error for PermissionRuleContextError {}
550
551#[derive(Clone, Debug, PartialEq, Eq)]
553pub enum PermissionContextKeyError {
554 InvalidFormat,
556 EmptyNamespace,
558 EmptyPath,
560 InvalidNamespace,
562 InvalidPath,
564 InvalidUnqualified(PermissionKeyError),
566}
567
568impl fmt::Display for PermissionContextKeyError {
569 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
570 match self {
571 Self::InvalidFormat => {
572 formatter.write_str("context key must be a name or namespaced id")
573 }
574 Self::EmptyNamespace => formatter.write_str("context key namespace is empty"),
575 Self::EmptyPath => formatter.write_str("context key path is empty"),
576 Self::InvalidNamespace => {
577 formatter.write_str("context key namespace contains invalid characters")
578 }
579 Self::InvalidPath => {
580 formatter.write_str("context key path contains invalid characters")
581 }
582 Self::InvalidUnqualified(source) => source.fmt(formatter),
583 }
584 }
585}
586
587impl Error for PermissionContextKeyError {}