Skip to main content

steel_core/permission/
rule_expression.rs

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/// A permission key and its optional rule-side context selector.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct PermissionRuleExpression {
13    key: PermissionKey,
14    context: PermissionRuleContext,
15}
16
17impl PermissionRuleExpression {
18    /// Creates a permission rule expression from validated parts.
19    #[must_use]
20    pub const fn new(key: PermissionKey, context: PermissionRuleContext) -> Self {
21        Self { key, context }
22    }
23
24    /// Parses `permission` or `permission{context=value,...}` syntax.
25    ///
26    /// # Errors
27    ///
28    /// Returns an error when the permission key or context selector is invalid.
29    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    /// Returns the permission key.
56    #[must_use]
57    pub const fn key(&self) -> &PermissionKey {
58        &self.key
59    }
60
61    /// Returns the rule-side context selector.
62    #[must_use]
63    pub const fn context(&self) -> &PermissionRuleContext {
64        &self.context
65    }
66
67    /// Splits the expression into its key and context.
68    #[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/// Invalid permission rule expression syntax.
249#[derive(Clone, Debug, PartialEq, Eq)]
250pub enum PermissionRuleExpressionError {
251    /// The permission key is invalid.
252    InvalidPermissionKey {
253        /// Invalid permission key text.
254        value: String,
255        /// Parse error.
256        source: PermissionKeyError,
257    },
258    /// A context selector starts with `{` but does not end with `}`.
259    UnclosedContext,
260    /// The context selector contains no entries.
261    EmptyContext,
262    /// A context entry is not `key=value`.
263    InvalidContextEntry(String),
264    /// A context entry contains an unsupported value.
265    InvalidContextValue {
266        /// Context key.
267        key: String,
268        /// Invalid context value.
269        value: String,
270    },
271    /// The same context key appears more than once.
272    DuplicateContextKey(String),
273    /// The built-in domain value is invalid.
274    InvalidDomain(String),
275    /// The built-in loaded-world value is invalid.
276    InvalidWorld(String),
277    /// A custom context key is invalid.
278    InvalidContextKey {
279        /// Invalid context key text.
280        key: String,
281        /// Parse error.
282        source: PermissionContextKeyError,
283    },
284    /// The combined rule-side context is invalid.
285    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 {}