Skip to main content

didbot_scope/
parse.rs

1//! Splitting one atom into its kind, its positional part and its
2//! parameters, as the permission spec's "Scope String Syntax" writes them.
3
4use percent_encoding::percent_decode_str;
5
6use crate::{ScopeParseError, ScopeWarning};
7
8/// Splits an atom into its kind and everything after it.
9///
10/// An atom names its kind before the first `:`, or before the first `?` when
11/// it has no positional value at all — `blob?accept=image/*` is the `blob`
12/// kind with no positional half, which is how the spec writes a scope
13/// carrying several values of one parameter.
14pub(crate) fn split_kind(atom: &str) -> Option<(&str, &str)> {
15    match (atom.find(':'), atom.find('?')) {
16        (Some(colon), query) if query.is_none_or(|q| colon < q) => {
17            Some((&atom[..colon], &atom[colon + 1..]))
18        }
19        (_, Some(query)) => Some(atom.split_at(query)),
20        _ => None,
21    }
22}
23
24/// One atom's positional part and `name=value` parameters, every value
25/// percent-decoded, and the warnings reading them has raised so far.
26///
27/// A kind takes the parameters it reads, and [`Syntax::finish`] ignores
28/// whatever is left. An empty positional part, as in `identity:?attr=handle`,
29/// gives no value.
30pub(crate) struct Syntax<'a> {
31    positional: Option<String>,
32    params: Vec<(&'a str, String)>,
33    warnings: Vec<ScopeWarning>,
34}
35
36impl<'a> Syntax<'a> {
37    /// Splits `atom`, whose kind [`split_kind`] found to be `kind`.
38    pub(crate) fn split(atom: &'a str, kind: &str) -> Result<Self, ScopeParseError> {
39        let after = &atom[kind.len()..];
40        let (positional, query) = match after.strip_prefix(':') {
41            Some(rest) => match rest.split_once('?') {
42                Some((positional, query)) => (positional, query),
43                None => (rest, ""),
44            },
45            None => ("", after.strip_prefix('?').unwrap_or_default()),
46        };
47        let params = query
48            .split('&')
49            .filter(|pair| !pair.is_empty())
50            .map(|pair| {
51                pair.split_once('=')
52                    .map(|(name, value)| (name, decode(value)))
53                    .ok_or_else(|| {
54                        ScopeParseError::MalformedQuery(pair.to_owned(), atom.to_owned())
55                    })
56            })
57            .collect::<Result<_, _>>()?;
58        Ok(Self {
59            positional: (!positional.is_empty()).then(|| decode(positional)),
60            params,
61            warnings: Vec::new(),
62        })
63    }
64
65    /// Every value of the list parameter `name`: the positional part when
66    /// `name` is its kind's positional parameter, then each `name=` in order.
67    pub(crate) fn list(&mut self, name: &str, positional: bool) -> Vec<String> {
68        let mut values: Vec<String> = match positional {
69            true => self.positional.take().into_iter().collect(),
70            false => Vec::new(),
71        };
72        let named = self.take(name);
73        if !values.is_empty() && !named.is_empty() {
74            self.warn(ScopeWarning::RepeatedList(name.to_owned()));
75        }
76        values.extend(named);
77        values
78    }
79
80    /// The parameter `name`, which takes one value: the positional part when
81    /// `name` is its kind's positional parameter, or its `name=`.
82    pub(crate) fn single(&mut self, name: &str, positional: bool) -> Option<String> {
83        let mut values = match positional {
84            true => self.positional.take().into_iter().collect(),
85            false => Vec::new(),
86        };
87        values.extend(self.take(name));
88        if values.len() > 1 {
89            self.warn(ScopeWarning::RepeatedValue(name.to_owned()));
90        }
91        values.into_iter().next()
92    }
93
94    /// Records `warning`, once.
95    pub(crate) fn warn(&mut self, warning: ScopeWarning) {
96        if !self.warnings.contains(&warning) {
97            self.warnings.push(warning);
98        }
99    }
100
101    /// Ends the read. Each parameter the kind did not take is ignored, with a
102    /// warning naming it.
103    pub(crate) fn finish(mut self) -> Vec<ScopeWarning> {
104        for (name, _) in std::mem::take(&mut self.params) {
105            self.warn(ScopeWarning::UnknownParameter(name.to_owned()));
106        }
107        self.warnings
108    }
109
110    /// Takes every `name=` value out of the parameters, in order.
111    fn take(&mut self, name: &str) -> Vec<String> {
112        let (taken, kept) = std::mem::take(&mut self.params)
113            .into_iter()
114            .partition(|(key, _)| *key == name);
115        self.params = kept;
116        taken.into_iter().map(|(_, value)| value).collect()
117    }
118}
119
120/// `value` percent-decoded. A byte sequence that is not UTF-8 becomes U+FFFD,
121/// which no NSID, MIME type or DID holds.
122fn decode(value: &str) -> String {
123    percent_decode_str(value).decode_utf8_lossy().into_owned()
124}