sophia_api/term/
var_name.rs

1//! I define the [`VarName`] wrapper type,
2//! which guarantees that the underlying `str`
3//! satisfies the `VARNAME` rule in [SPARQL](https://www.w3.org/TR/sparql11-query/#rVARNAME).
4
5use super::*;
6use lazy_static::lazy_static;
7use regex::Regex;
8use sophia_iri::wrap;
9use std::borrow::Borrow;
10use std::fmt::Debug;
11use thiserror::Error;
12
13lazy_static! {
14    /// Production of SPARQL's VARNAME according to the
15    /// [SPARQL spec](https://www.w3.org/TR/sparql11-query/#rVARNAME).
16    ///
17    /// # Captures
18    ///
19    /// This regular expression matches the whole input (`^...$`),
20    /// therefore, it can not be used to capture `VARNAME`s in an arbitrary string.
21    ///
22    /// # Rule
23    ///
24    /// `VARNAME ::= ( PN_CHARS_U | [0-9] ) ( PN_CHARS_U | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040] )*`
25    static ref VARNAME: Regex = Regex::new(r"(?x)
26      ^
27      [_A-Za-z0-9\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}\U{10000}-\U{EFFFF}]
28      [_A-Za-z0-9\u{B7}\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{300}-\u{37D}\u{37F}-\u{1FFF}\u{200C}-\u{200D}\u{203F}-\u{2040}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFFD}\U{10000}-\U{EFFFF}]*
29      $
30    ").unwrap();
31}
32
33wrap! { VarName borrowing str :
34    /// This wrapper guarantees that the underlying `str`
35    /// satisfies the `VARNAME` rule in [SPARQL](https://www.w3.org/TR/sparql11-query/#rVARNAME).
36    pub fn new(name: T) -> Result<Self, InvalidVarName> {
37        if VARNAME.is_match(name.borrow()) {
38            Ok(VarName(name))
39        } else {
40            Err(InvalidVarName(name.borrow().to_string()))
41        }
42    }
43
44    /// Gets a reference to the underlying &str.
45    pub fn as_str(&self) -> &str {
46        self.0.borrow()
47    }
48}
49/// This error is raised when trying to parse an invalid variable name.
50#[derive(Debug, Error)]
51#[error("The given variable name '{0}' does not comply with SPARQL's VARNAME")]
52pub struct InvalidVarName(pub String);
53
54impl<T> Term for VarName<T>
55where
56    T: Borrow<str> + Debug,
57{
58    type BorrowTerm<'x> = &'x Self where T: 'x;
59
60    fn kind(&self) -> TermKind {
61        TermKind::Variable
62    }
63    fn variable(&self) -> Option<VarName<MownStr>> {
64        Some(self.as_ref().map_unchecked(MownStr::from_str))
65    }
66    fn borrow_term(&self) -> Self::BorrowTerm<'_> {
67        self
68    }
69}
70
71#[cfg(test)]
72mod test {
73    use super::*;
74    use test_case::test_case;
75
76    #[test_case("x")]
77    #[test_case("foo_bar_baz")]
78    #[test_case("hé_hé")]
79    #[test_case("1")]
80    #[test_case("abc42")]
81    fn valid(tag: &str) {
82        assert!(VarName::new(tag).is_ok());
83    }
84
85    #[test_case(""; "empty")]
86    #[test_case(" "; "space")]
87    #[test_case("."; "dot")]
88    #[test_case("a.b"; "with dot")]
89    #[test_case("a,b"; "with comma")]
90    #[test_case("a b"; "with space")]
91    fn invalid(tag: &str) {
92        assert!(VarName::new(tag).is_err());
93    }
94}