steel_core/permission/
key.rs1use std::{error::Error, fmt};
2
3#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
5pub struct PermissionKey(String);
6
7impl PermissionKey {
8 pub fn parse(value: impl Into<String>) -> Result<Self, PermissionKeyError> {
14 let value = value.into();
15 if value.is_empty() {
16 return Err(PermissionKeyError::Empty);
17 }
18
19 let segments = value.split('.').collect::<Vec<_>>();
20 for (index, segment) in segments.iter().enumerate() {
21 if segment.is_empty() {
22 return Err(PermissionKeyError::EmptySegment);
23 }
24 if *segment == "*" {
25 if index + 1 != segments.len() {
26 return Err(PermissionKeyError::WildcardNotFinal);
27 }
28 continue;
29 }
30 if segment.contains('*') {
31 return Err(PermissionKeyError::InvalidWildcardSegment);
32 }
33 validate_permission_segment(segment)?;
34 }
35
36 Ok(Self(value))
37 }
38
39 pub fn from_segments(
45 segments: impl IntoIterator<Item = PermissionSegment>,
46 ) -> Result<Self, PermissionKeyError> {
47 let mut value = String::new();
48 for segment in segments {
49 if !value.is_empty() {
50 value.push('.');
51 }
52 value.push_str(segment.as_str());
53 }
54 if value.is_empty() {
55 return Err(PermissionKeyError::Empty);
56 }
57 Ok(Self(value))
58 }
59
60 #[must_use]
62 pub fn as_str(&self) -> &str {
63 &self.0
64 }
65
66 pub fn child(&self, segment: &PermissionSegment) -> Result<Self, PermissionKeyError> {
72 Self::parse(format!("{}.{}", self.0, segment.as_str()))
73 }
74
75 #[must_use]
77 pub fn matches(&self, other: &Self) -> bool {
78 if self.0 == "*" {
79 return true;
80 }
81 let Some(prefix) = self.0.strip_suffix(".*") else {
82 return self == other;
83 };
84 other
85 .0
86 .strip_prefix(prefix)
87 .is_some_and(|remaining| remaining.starts_with('.'))
88 }
89
90 pub(super) fn specificity(&self) -> usize {
91 if self.0 == "*" {
92 return 0;
93 }
94 self.0
95 .strip_suffix(".*")
96 .map_or(self.0.as_str(), |prefix| prefix)
97 .split('.')
98 .count()
99 }
100
101 pub(super) fn scopes(&self, key: &Self) -> bool {
102 if self == key {
103 return true;
104 }
105 key.0
106 .strip_prefix(self.as_str())
107 .is_some_and(|remaining| remaining.starts_with('.'))
108 }
109}
110
111#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
113pub struct PermissionSegment(String);
114
115impl PermissionSegment {
116 pub fn parse(value: impl Into<String>) -> Result<Self, PermissionKeyError> {
123 let value = value.into();
124 if value.is_empty() {
125 return Err(PermissionKeyError::EmptySegment);
126 }
127 if value.contains('*') {
128 return Err(PermissionKeyError::InvalidWildcardSegment);
129 }
130 validate_permission_segment(&value)?;
131 Ok(Self(value))
132 }
133
134 #[must_use]
136 pub fn as_str(&self) -> &str {
137 &self.0
138 }
139}
140
141fn validate_permission_segment(segment: &str) -> Result<(), PermissionKeyError> {
142 if segment.bytes().all(|byte| {
143 byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_' || byte == b'-'
144 }) {
145 Ok(())
146 } else {
147 Err(PermissionKeyError::InvalidSegment)
148 }
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq)]
153pub enum PermissionKeyError {
154 Empty,
156 EmptySegment,
158 WildcardNotFinal,
160 InvalidWildcardSegment,
162 InvalidSegment,
164}
165
166impl fmt::Display for PermissionKeyError {
167 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
168 match self {
169 Self::Empty => formatter.write_str("permission key is empty"),
170 Self::EmptySegment => formatter.write_str("permission key contains an empty segment"),
171 Self::WildcardNotFinal => {
172 formatter.write_str("permission wildcard must be the final segment")
173 }
174 Self::InvalidWildcardSegment => {
175 formatter.write_str("permission wildcard must occupy the full segment")
176 }
177 Self::InvalidSegment => formatter.write_str(
178 "permission segment must contain only lowercase letters, numbers, '_' or '-'",
179 ),
180 }
181 }
182}
183
184impl Error for PermissionKeyError {}