precis_profiles/
passwords.rs1use crate::common;
2use lazy_static::lazy_static;
3use precis_core::profile::{PrecisFastInvocation, Profile, Rules};
4use precis_core::Error;
5use precis_core::{FreeformClass, StringClass};
6use std::borrow::Cow;
7
8#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
36pub struct OpaqueString(FreeformClass);
37
38impl OpaqueString {
39 pub fn new() -> Self {
41 Self(FreeformClass::default())
42 }
43}
44
45impl Profile for OpaqueString {
46 fn prepare<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
47 where
48 S: Into<Cow<'a, str>>,
49 {
50 let s = s.into();
51 let s = (!s.is_empty()).then_some(s).ok_or(Error::Invalid)?;
52 self.0.allows(&s)?;
53 Ok(s)
54 }
55
56 fn enforce<'a, S>(&self, s: S) -> Result<Cow<'a, str>, Error>
57 where
58 S: Into<Cow<'a, str>>,
59 {
60 let s = self.prepare(s)?;
61 let s = self.additional_mapping_rule(s)?;
62 let s = self.normalization_rule(s)?;
63 (!s.is_empty()).then_some(s).ok_or(Error::Invalid)
64 }
65
66 fn compare<A, B>(&self, s1: A, s2: B) -> Result<bool, Error>
67 where
68 A: AsRef<str>,
69 B: AsRef<str>,
70 {
71 Ok(self.enforce(s1.as_ref())? == self.enforce(s2.as_ref())?)
72 }
73}
74
75impl Rules for OpaqueString {
76 fn additional_mapping_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
77 where
78 T: Into<Cow<'a, str>>,
79 {
80 let s = s.into();
81 match s.find(common::is_non_ascii_space) {
82 None => Ok(s),
83 Some(pos) => {
84 let mut res = String::from(&s[..pos]);
85 res.reserve(s.len() - res.len());
86 for c in s[pos..].chars() {
87 if common::is_non_ascii_space(c) {
88 res.push(common::SPACE);
89 } else {
90 res.push(c);
91 }
92 }
93 Ok(res.into())
94 }
95 }
96 }
97
98 fn normalization_rule<'a, T>(&self, s: T) -> Result<Cow<'a, str>, Error>
99 where
100 T: Into<Cow<'a, str>>,
101 {
102 common::normalization_form_nfc(s)
103 }
104}
105
106fn get_opaque_string_profile() -> &'static OpaqueString {
107 lazy_static! {
108 static ref OPAQUE_STRING: OpaqueString = OpaqueString::default();
109 }
110 &OPAQUE_STRING
111}
112
113impl PrecisFastInvocation for OpaqueString {
114 fn prepare<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
115 where
116 S: Into<Cow<'a, str>>,
117 {
118 get_opaque_string_profile().prepare(s)
119 }
120
121 fn enforce<'a, S>(s: S) -> Result<Cow<'a, str>, Error>
122 where
123 S: Into<Cow<'a, str>>,
124 {
125 get_opaque_string_profile().enforce(s)
126 }
127
128 fn compare<A, B>(s1: A, s2: B) -> Result<bool, Error>
129 where
130 A: AsRef<str>,
131 B: AsRef<str>,
132 {
133 get_opaque_string_profile().compare(s1, s2)
134 }
135}
136
137#[cfg(test)]
138mod test_passwords {
139 use crate::passwords::*;
140
141 #[test]
142 fn opaque_string_profile() {
143 let profile = OpaqueString::new();
144
145 let res = profile.prepare("πßå");
146 assert_eq!(res, Ok(Cow::from("πßå")));
147
148 let res = profile.enforce("πßå");
149 assert_eq!(res, Ok(Cow::from("πßå")));
150
151 let res = profile.compare("Secret", "Secret");
152 assert_eq!(res, Ok(true));
153 }
154}