1use std::{collections::BTreeSet, error::Error, fmt};
2
3use steel_utils::Identifier;
4
5use super::{
6 PermissionContextKey, PermissionContextKeyError, PermissionKey, PermissionKeyError,
7 PermissionRuleContext, PermissionRuleContextError,
8};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct PermissionRuleExpression {
13 key: PermissionKey,
14 context: PermissionRuleContext,
15}
16
17impl PermissionRuleExpression {
18 #[must_use]
20 pub const fn new(key: PermissionKey, context: PermissionRuleContext) -> Self {
21 Self { key, context }
22 }
23
24 pub fn parse(value: impl Into<String>) -> Result<Self, PermissionRuleExpressionError> {
30 let value = value.into();
31 let Some(context_start) = value.find('{') else {
32 let key = PermissionKey::parse(value.as_str()).map_err(|source| {
33 PermissionRuleExpressionError::InvalidPermissionKey { value, source }
34 })?;
35 return Ok(Self::new(key, PermissionRuleContext::Global));
36 };
37
38 if !value.ends_with('}') {
39 return Err(PermissionRuleExpressionError::UnclosedContext);
40 }
41
42 let key_value = &value[..context_start];
43 let key = PermissionKey::parse(key_value).map_err(|source| {
44 PermissionRuleExpressionError::InvalidPermissionKey {
45 value: key_value.to_owned(),
46 source,
47 }
48 })?;
49 let context_value = &value[context_start + 1..value.len() - 1];
50 let context = parse_context(context_value)
51 .map_err(PermissionRuleExpressionError::from_context_error)?;
52 Ok(Self::new(key, context))
53 }
54
55 #[must_use]
57 pub const fn key(&self) -> &PermissionKey {
58 &self.key
59 }
60
61 #[must_use]
63 pub const fn context(&self) -> &PermissionRuleContext {
64 &self.context
65 }
66
67 #[must_use]
69 pub fn into_parts(self) -> (PermissionKey, PermissionRuleContext) {
70 (self.key, self.context)
71 }
72}
73
74impl fmt::Display for PermissionRuleExpression {
75 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
76 formatter.write_str(self.key.as_str())?;
77 write_context(formatter, &self.context)
78 }
79}
80
81pub(super) fn parse_context(
82 value: &str,
83) -> Result<PermissionRuleContext, PermissionExpressionContextError> {
84 if value.is_empty() {
85 return Err(PermissionExpressionContextError::EmptyContext);
86 }
87
88 let mut contexts = Vec::new();
89 let mut seen_keys = BTreeSet::new();
90 for entry in value.split(',') {
91 let Some((key, context_value)) = entry.split_once('=') else {
92 return Err(PermissionExpressionContextError::InvalidContextEntry(
93 entry.to_owned(),
94 ));
95 };
96 if key.is_empty() || context_value.is_empty() {
97 return Err(PermissionExpressionContextError::InvalidContextEntry(
98 entry.to_owned(),
99 ));
100 }
101 if context_value
102 .chars()
103 .any(|character| character.is_whitespace() || "{},=".contains(character))
104 {
105 return Err(PermissionExpressionContextError::InvalidContextValue {
106 key: key.to_owned(),
107 value: context_value.to_owned(),
108 });
109 }
110 if !seen_keys.insert(key) {
111 return Err(PermissionExpressionContextError::DuplicateContextKey(
112 key.to_owned(),
113 ));
114 }
115
116 let context = match key {
117 "domain" => parse_domain_context(context_value)?,
118 "world" => parse_world_context(context_value)?,
119 custom_key => parse_custom_context(custom_key, context_value)?,
120 };
121 contexts.push(context);
122 }
123
124 PermissionRuleContext::all(contexts)
125 .map_err(PermissionExpressionContextError::InvalidRuleContext)
126}
127
128fn parse_domain_context(
129 value: &str,
130) -> Result<PermissionRuleContext, PermissionExpressionContextError> {
131 if value.is_empty() || !Identifier::validate_namespace(value) {
132 return Err(PermissionExpressionContextError::InvalidDomain(
133 value.to_owned(),
134 ));
135 }
136 PermissionRuleContext::domain(value)
137 .map_err(PermissionExpressionContextError::InvalidRuleContext)
138}
139
140fn parse_world_context(
141 value: &str,
142) -> Result<PermissionRuleContext, PermissionExpressionContextError> {
143 let Some((domain, name)) = value.split_once(':') else {
144 return Err(PermissionExpressionContextError::InvalidWorld(
145 value.to_owned(),
146 ));
147 };
148 if domain.is_empty()
149 || name.is_empty()
150 || name.contains([':', '/'])
151 || !Identifier::validate_namespace(domain)
152 || !Identifier::validate_path(name)
153 {
154 return Err(PermissionExpressionContextError::InvalidWorld(
155 value.to_owned(),
156 ));
157 }
158 Ok(PermissionRuleContext::world(Identifier::new(
159 domain.to_owned(),
160 name.to_owned(),
161 )))
162}
163
164fn parse_custom_context(
165 key: &str,
166 value: &str,
167) -> Result<PermissionRuleContext, PermissionExpressionContextError> {
168 let key = PermissionContextKey::parse(key).map_err(|source| {
169 PermissionExpressionContextError::InvalidContextKey {
170 key: key.to_owned(),
171 source,
172 }
173 })?;
174 PermissionRuleContext::custom(key, value)
175 .map_err(PermissionExpressionContextError::InvalidRuleContext)
176}
177
178pub(super) fn write_context(
179 formatter: &mut fmt::Formatter<'_>,
180 context: &PermissionRuleContext,
181) -> fmt::Result {
182 if matches!(context, PermissionRuleContext::Global) {
183 return Ok(());
184 }
185
186 formatter.write_str("{")?;
187 write_context_entries(formatter, context, &mut true)?;
188 formatter.write_str("}")
189}
190
191fn write_context_entries(
192 formatter: &mut fmt::Formatter<'_>,
193 context: &PermissionRuleContext,
194 first: &mut bool,
195) -> fmt::Result {
196 match context {
197 PermissionRuleContext::Global => Ok(()),
198 PermissionRuleContext::Domain(domain) => {
199 write_context_entry(formatter, first, "domain", domain)
200 }
201 PermissionRuleContext::World(world) => {
202 write_context_entry(formatter, first, "world", world)
203 }
204 PermissionRuleContext::Custom { key, value } => {
205 write_context_entry(formatter, first, key.as_str(), value)
206 }
207 PermissionRuleContext::All(contexts) => {
208 for context in contexts.iter() {
209 write_context_entries(formatter, context, first)?;
210 }
211 Ok(())
212 }
213 }
214}
215
216fn write_context_entry(
217 formatter: &mut fmt::Formatter<'_>,
218 first: &mut bool,
219 key: &str,
220 value: impl fmt::Display,
221) -> fmt::Result {
222 if *first {
223 *first = false;
224 } else {
225 formatter.write_str(",")?;
226 }
227 write!(formatter, "{key}={value}")
228}
229
230#[derive(Clone, Debug, PartialEq, Eq)]
231pub(super) enum PermissionExpressionContextError {
232 EmptyContext,
233 InvalidContextEntry(String),
234 InvalidContextValue {
235 key: String,
236 value: String,
237 },
238 DuplicateContextKey(String),
239 InvalidDomain(String),
240 InvalidWorld(String),
241 InvalidContextKey {
242 key: String,
243 source: PermissionContextKeyError,
244 },
245 InvalidRuleContext(PermissionRuleContextError),
246}
247
248#[derive(Clone, Debug, PartialEq, Eq)]
250pub enum PermissionRuleExpressionError {
251 InvalidPermissionKey {
253 value: String,
255 source: PermissionKeyError,
257 },
258 UnclosedContext,
260 EmptyContext,
262 InvalidContextEntry(String),
264 InvalidContextValue {
266 key: String,
268 value: String,
270 },
271 DuplicateContextKey(String),
273 InvalidDomain(String),
275 InvalidWorld(String),
277 InvalidContextKey {
279 key: String,
281 source: PermissionContextKeyError,
283 },
284 InvalidRuleContext(PermissionRuleContextError),
286}
287
288impl PermissionRuleExpressionError {
289 pub(super) fn from_context_error(error: PermissionExpressionContextError) -> Self {
290 match error {
291 PermissionExpressionContextError::EmptyContext => Self::EmptyContext,
292 PermissionExpressionContextError::InvalidContextEntry(entry) => {
293 Self::InvalidContextEntry(entry)
294 }
295 PermissionExpressionContextError::InvalidContextValue { key, value } => {
296 Self::InvalidContextValue { key, value }
297 }
298 PermissionExpressionContextError::DuplicateContextKey(key) => {
299 Self::DuplicateContextKey(key)
300 }
301 PermissionExpressionContextError::InvalidDomain(domain) => Self::InvalidDomain(domain),
302 PermissionExpressionContextError::InvalidWorld(world) => Self::InvalidWorld(world),
303 PermissionExpressionContextError::InvalidContextKey { key, source } => {
304 Self::InvalidContextKey { key, source }
305 }
306 PermissionExpressionContextError::InvalidRuleContext(source) => {
307 Self::InvalidRuleContext(source)
308 }
309 }
310 }
311}
312
313impl fmt::Display for PermissionRuleExpressionError {
314 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
315 match self {
316 Self::InvalidPermissionKey { value, source } => {
317 write!(formatter, "invalid permission key '{value}': {source}")
318 }
319 Self::UnclosedContext => {
320 formatter.write_str("permission context selector is not closed")
321 }
322 Self::EmptyContext => formatter.write_str("permission context selector is empty"),
323 Self::InvalidContextEntry(entry) => {
324 write!(formatter, "invalid permission context entry '{entry}'")
325 }
326 Self::InvalidContextValue { key, value } => write!(
327 formatter,
328 "invalid permission context value '{value}' for '{key}'"
329 ),
330 Self::DuplicateContextKey(key) => {
331 write!(
332 formatter,
333 "permission context key '{key}' appears more than once"
334 )
335 }
336 Self::InvalidDomain(domain) => {
337 write!(formatter, "invalid domain context '{domain}'")
338 }
339 Self::InvalidWorld(world) => write!(formatter, "invalid world context '{world}'"),
340 Self::InvalidContextKey { key, source } => {
341 write!(
342 formatter,
343 "invalid permission context key '{key}': {source}"
344 )
345 }
346 Self::InvalidRuleContext(source) => source.fmt(formatter),
347 }
348 }
349}
350
351impl Error for PermissionRuleExpressionError {}