precis_core/
profile.rs

1//! This module contains traits with operations and rules that profiles
2//! must implement such as it is defined by the PRECIS framework
3//! [`rfc8264`](https://datatracker.ietf.org/doc/html/rfc8264#section-5)
4
5use crate::{Error, UnexpectedError};
6use std::borrow::Cow;
7
8/// Rules that any profile of a PRECIS string class MUST define
9/// to proper manage the handling of right-to-left code points as
10/// well as various mapping operations such as case preservation
11/// or lower casing, Unicode normalization, mapping of certain code
12/// points to other code points or to nothing, and mapping of full width
13/// and half width code points.
14pub trait Rules {
15    /// Applies the width mapping rule of a profile to an input string.
16    /// # Arguments:
17    /// * `s`: String value
18    /// # Returns
19    /// The same string if no modifications were required or a new allocated
20    /// string if `s` was modified as a result of applying this rule
21    fn width_mapping_rule<'a, T>(&self, _s: T) -> Result<Cow<'a, str>, Error>
22    where
23        T: Into<Cow<'a, str>>,
24    {
25        Err(Error::Unexpected(UnexpectedError::ProfileRuleNotApplicable))
26    }
27
28    /// Applies the additional mapping rule of a profile to an input string.
29    /// # Arguments:
30    /// * `s`: String value
31    /// # Returns
32    /// The same string if no modifications were required or a new allocated
33    /// string if `s` was modified as a result of applying this rule
34    fn additional_mapping_rule<'a, T>(&self, _s: T) -> Result<Cow<'a, str>, Error>
35    where
36        T: Into<Cow<'a, str>>,
37    {
38        Err(Error::Unexpected(UnexpectedError::ProfileRuleNotApplicable))
39    }
40
41    /// Applies the case mapping rule of a profile to an input string
42    /// # Arguments:
43    /// * `s`: String value
44    /// # Returns
45    /// The same string if no modifications were required or a new allocated
46    /// string if `s` was modified as a result of applying this rule
47    fn case_mapping_rule<'a, T>(&self, _s: T) -> Result<Cow<'a, str>, Error>
48    where
49        T: Into<Cow<'a, str>>,
50    {
51        Err(Error::Unexpected(UnexpectedError::ProfileRuleNotApplicable))
52    }
53
54    /// Applies the normalization rule of a profile to an input string
55    /// # Arguments:
56    /// * `s`: String value
57    /// # Returns
58    /// The same string if no modifications were required or a new allocated
59    /// string if `s` was modified as a result of applying this rule
60    fn normalization_rule<'a, T>(&self, _s: T) -> Result<Cow<'a, str>, Error>
61    where
62        T: Into<Cow<'a, str>>,
63    {
64        Err(Error::Unexpected(UnexpectedError::ProfileRuleNotApplicable))
65    }
66
67    /// Applies the directionality rule of a profile to an input string
68    /// # Arguments:
69    /// * `s`: String value
70    /// # Returns
71    /// The same string if no modifications were required or a new allocated
72    /// string if `s` was modified as a result of applying this rule
73    fn directionality_rule<'a, T>(&self, _s: T) -> Result<Cow<'a, str>, Error>
74    where
75        T: Into<Cow<'a, str>>,
76    {
77        Err(Error::Unexpected(UnexpectedError::ProfileRuleNotApplicable))
78    }
79}
80
81/// Profile enables application protocols to apply the string classes in ways that
82/// are appropriate for common constructs.
83pub trait Profile {
84    /// Ensures that the code points in a single input string are allowed
85    /// by the underlying PRECIS string class, and sometimes also entails
86    /// applying one or more of the rules specified for a particular string
87    /// class or profile thereof.
88    /// # Arguments:
89    /// * `s`: String value
90    /// # Returns
91    /// The same string if no modification were required or a new allocated
92    /// string if `s` needed further modifications as a result of applying the
93    /// rules defined by this profile to prepare the string
94    fn prepare<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
95    where
96        S: Into<Cow<'a, str>>;
97
98    /// Applies all the rules specified for a particular string class,
99    /// or profile thereof, to a single input string, for the purpose of
100    /// checking whether the string conforms to all the rules and thus
101    /// determining if the string can be used in a given protocol slot.
102    /// # Arguments:
103    /// * `s`: String value
104    /// # Returns
105    /// The same string if no modification were required or a new allocated
106    /// string if `s` needed further modifications as a result of enforcing
107    /// the string according to the rules defined by this profile.
108    fn enforce<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
109    where
110        S: Into<Cow<'a, str>>;
111
112    /// Comparison entails applying all the rules specified for a
113    /// particular string class, or profile thereof, to two separate input
114    /// strings, for the purpose of determining if the two strings are
115    /// equivalent.
116    fn compare<A, B>(&self, s1: A, s2: B) -> Result<bool, Error>
117    where
118        A: AsRef<str>,
119        B: AsRef<str>;
120}
121
122/// Fast invocation trait that allows profiles to be used without providing
123/// a specific instance. This is usually achieved by using a static instance
124/// allocated with [`lazy_static`](https://docs.rs/lazy_static/1.4.0/lazy_static)
125pub trait PrecisFastInvocation {
126    /// Ensures that the code points in a single input string are allowed
127    /// by the underlying PRECIS string class, and sometimes also entails
128    /// applying one or more of the rules specified for a particular string
129    /// class or profile thereof.
130    /// # Arguments:
131    /// * `s`: String value
132    /// # Returns
133    /// The same string if no modification were required or a new allocated
134    /// string if `s` needed further modifications as a result of applying the
135    /// rules defined by this profile to prepare the string
136    fn prepare<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
137    where
138        S: Into<Cow<'a, str>>;
139
140    /// Applies all rules specified for a particular string class,
141    /// or profile thereof, to a single input string, for the purpose of
142    /// checking whether the string conforms to all the rules and thus
143    /// determining if the string can be used in a given protocol slot.
144    /// # Arguments:
145    /// * `s`: String value
146    /// # Returns
147    /// The same string if no modification were required or a new allocated
148    /// string if `s` needed further modifications as a result of enforcing
149    /// the string according to the rules defined by this profile.
150    fn enforce<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
151    where
152        S: Into<Cow<'a, str>>;
153
154    /// Comparison entails applying all the rules specified for a
155    /// particular string class, or profile thereof, to two separate input
156    /// strings, for the purpose of determining if the two strings are
157    /// equivalent.
158    fn compare<A, B>(s1: A, s2: B) -> Result<bool, Error>
159    where
160        A: AsRef<str>,
161        B: AsRef<str>;
162}
163
164/// Apply rules until the string is stable. Some profiles, especially those
165/// that the result of applying these rules does not result in an idempotent
166/// operation for all code points SHOULD apply the rules repeatedly until
167/// the output string is stable.
168/// # Arguments:
169/// * `s`: String value
170/// * `f`: Callback to invoke to apply the rules to `s`
171/// # Returns
172/// The stable string after applying the rules; if the output string
173/// does not stabilize after reapplying the rules three (3) additional times
174/// after the first application, the string is rejected as invalid.
175pub fn stabilize<'a, F, S>(s: S, f: F) -> Result<Cow<'a, str>, Error>
176where
177    S: Into<Cow<'a, str>>,
178    F: for<'b> Fn(&'b str) -> Result<Cow<'b, str>, Error>,
179{
180    let mut c = s.into();
181    for _i in 0..=2 {
182        let tmp = f(&c)?;
183        if tmp == c {
184            return Ok(c);
185        }
186
187        // Strings are not equal, so we have an owned copy.
188        // We move the owned string without copying it for
189        // the next iteration
190        c = Cow::from(tmp.into_owned());
191    }
192
193    // The string did not stabilized after applying the rules three times.
194    Err(Error::Invalid)
195}
196
197#[cfg(test)]
198mod profiles {
199    use super::*;
200
201    #[derive(Default, Debug)]
202    struct TestDefaultRule {}
203    impl Rules for TestDefaultRule {}
204
205    #[test]
206    fn test_default_rule() {
207        let rule = TestDefaultRule::default();
208
209        assert_eq!(
210            rule.width_mapping_rule("Test"),
211            Err(Error::Unexpected(UnexpectedError::ProfileRuleNotApplicable))
212        );
213
214        assert_eq!(
215            rule.additional_mapping_rule("Test"),
216            Err(Error::Unexpected(UnexpectedError::ProfileRuleNotApplicable))
217        );
218
219        assert_eq!(
220            rule.case_mapping_rule("Test"),
221            Err(Error::Unexpected(UnexpectedError::ProfileRuleNotApplicable))
222        );
223
224        assert_eq!(
225            rule.normalization_rule("Test"),
226            Err(Error::Unexpected(UnexpectedError::ProfileRuleNotApplicable))
227        );
228
229        assert_eq!(
230            rule.directionality_rule("Test"),
231            Err(Error::Unexpected(UnexpectedError::ProfileRuleNotApplicable))
232        );
233    }
234}