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}