elevator_core/components/access.rs
1//! Access control component for restricting rider stop access.
2
3use crate::entity::EntityId;
4use serde::{Deserialize, Serialize};
5use std::collections::HashSet;
6
7/// Per-rider access control: which stops the rider is allowed to visit.
8///
9/// When absent from a rider entity, the rider has unrestricted access.
10/// When present, [`can_access`](Self::can_access) returns `true` either
11/// when the queried stop is in [`allowed_stops`](Self::allowed_stops)
12/// or when the set is empty — an empty set means "no restriction"
13/// (matches `AccessControl::default()` and the principle-of-least-
14/// surprise that an empty allowlist shouldn't silently block all access).
15/// To explicitly block all stops, use a sentinel set or a dedicated
16/// no-access wrapper. (#289)
17#[derive(Debug, Clone, Default, Serialize, Deserialize)]
18pub struct AccessControl {
19 /// Set of stop `EntityId`s this rider may visit.
20 ///
21 /// **Semantics**: empty set ⇒ unrestricted (allow all). Non-empty
22 /// set ⇒ allowlist; `can_access(stop)` is true iff `stop` is in
23 /// the set.
24 allowed_stops: HashSet<EntityId>,
25}
26
27impl AccessControl {
28 /// Create a new access control with the given set of allowed stops.
29 /// Pass an empty set for "no restriction" (unrestricted access).
30 #[must_use]
31 pub const fn new(allowed_stops: HashSet<EntityId>) -> Self {
32 Self { allowed_stops }
33 }
34
35 /// Check if the rider can access the given stop.
36 ///
37 /// An empty `allowed_stops` set is treated as "no restriction" —
38 /// `can_access` returns `true` for any stop. A non-empty set is an
39 /// allowlist; `stop` must be a member.
40 #[must_use]
41 pub fn can_access(&self, stop: EntityId) -> bool {
42 self.allowed_stops.is_empty() || self.allowed_stops.contains(&stop)
43 }
44
45 /// The set of allowed stop entity IDs. Empty means unrestricted.
46 #[must_use]
47 pub const fn allowed_stops(&self) -> &HashSet<EntityId> {
48 &self.allowed_stops
49 }
50}