1use chumsky::{
10 error::Rich, extra, prelude::{any, just, none_of, one_of},
11 IterParser, Parser
12};
13use itertools::{EitherOrBoth, Itertools};
14use serde_with::{DeserializeFromStr, SerializeDisplay};
15use std::{cmp::Ordering, fmt::Display, str::FromStr};
16use thiserror::Error;
17
18#[derive(Debug, DeserializeFromStr, SerializeDisplay, Clone, PartialEq, Eq)]
36pub struct SimpleDN {
37 rdns: Vec<SimpleRDN>,
45}
46
47impl Display for SimpleDN {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 write!(f, "{}", self.rdns.iter().format(","))
51 }
52}
53
54impl FromStr for SimpleDN {
55 type Err = SimpleDnParseError;
56
57 fn from_str(s: &str) -> Result<Self, Self::Err> {
58 match simple_dn_parser().parse(s).into_result() {
59 Ok(simple_rdn) => Ok(simple_rdn),
60 Err(rich_errors) => Err(SimpleDnParseError {
61 errors: rich_errors
62 .into_iter()
63 .map(|rich_err| ToString::to_string(&rich_err))
65 .collect(),
66 }),
67 }
68 }
69}
70
71impl PartialOrd for SimpleDN {
74 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
75 let most_significant_differing_rdn = self
76 .rdns
77 .iter()
78 .rev()
79 .zip_longest(other.rdns.iter().rev())
80 .find(
81 |maybe_both| !matches!(maybe_both, EitherOrBoth::Both(this, that) if this == that),
82 );
83
84 match most_significant_differing_rdn {
85 None => Some(Ordering::Equal),
87 Some(maybe_both) => match maybe_both {
88 EitherOrBoth::Both(_, _) => None,
90 EitherOrBoth::Left(_) => Some(Ordering::Less),
93 EitherOrBoth::Right(_) => Some(Ordering::Greater),
94 },
95 }
96 }
97}
98
99pub fn common_ancestor(left: &SimpleDN, right: &SimpleDN) -> Option<SimpleDN> {
102 let mut common_rdns = left.rdns
103 .iter()
104 .rev()
105 .zip(right.rdns.iter().rev())
106 .take_while(|(left, right)| left == right)
107 .map(|(left, _)| left.clone())
109 .collect_vec();
110
111 common_rdns.reverse();
114
115 if common_rdns.is_empty() {
116 None
118 }
119 else {
120 Some(SimpleDN { rdns: common_rdns })
121 }
122}
123
124fn simple_dn_parser<'src>() -> impl Parser<'src, &'src str, SimpleDN, extra::Err<Rich<'src, char>>>
125{
126 simple_rdn_parser()
127 .separated_by(just(','))
129 .collect::<Vec<SimpleRDN>>()
130 .map(|rdns| SimpleDN { rdns })
131}
132
133impl SimpleDN {
135 pub fn get(&self, key: &str) -> Option<&str> {
141 self.rdns
142 .iter()
143 .find(|rdn| rdn.key == key)
144 .map(|rdn| rdn.value.as_str())
145 }
146
147 pub fn get_starting_from(&self, key: &str) -> Option<SimpleDN> {
149 self.rdns
150 .iter()
151 .position(|rdn| rdn.key == key)
152 .map(|position| {
153 let (_, tail) = self.rdns.as_slice().split_at(position);
154
155 SimpleDN {
156 rdns: tail.to_owned(),
157 }
158 })
159 }
160
161 pub fn get_type(&self) -> &str {
169 #[allow(clippy::expect_used, reason = "Relying on struct invariant.")]
170 &self
171 .rdns
172 .first()
173 .expect("Invariant violation. SimpleDN should never be empty.")
174 .key
175 }
176
177 pub fn parent(&self) -> Option<SimpleDN> {
181 match self.rdns.as_slice() {
182 [_, rest @ ..] if !rest.is_empty() => Some(SimpleDN {
183 rdns: rest.to_owned(),
184 }),
185 _ => None,
186 }
187 }
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
198#[display("{key}={value}")]
199struct SimpleRDN {
200 pub key: String,
205 pub value: String,
206}
207
208fn simple_rdn_parser<'src>() -> impl Parser<'src, &'src str, SimpleRDN, extra::Err<Rich<'src, char>>>
214{
215 let rdn_key = any()
216 .filter(|c: &char| c.is_ascii_alphanumeric())
218 .repeated()
219 .at_least(1)
220 .collect::<String>()
221 .then_ignore(just('='));
223
224 let special = r##",\#+<>;"="##;
229
230 let escaped = just('\\').then(one_of(special))
238 .to_slice();
241
242 let rdn_value = none_of(special)
245 .to_slice()
247 .or(escaped)
248 .repeated()
249 .at_least(1)
250 .to_slice()
251 .map(ToString::to_string);
252
253 rdn_key
255 .then(rdn_value)
256 .map(|(key, value)| SimpleRDN { key, value })
257}
258
259#[derive(Error, Debug)]
260#[error("Couldn't parse DN: {:?}", self.errors)]
261pub struct SimpleDnParseError {
262 errors: Vec<String>,
265}
266
267#[cfg(test)]
268mod tests {
269
270 use super::*;
271 use serde::{Deserialize, Serialize};
272
273 static EXAMPLE_DN: &str = "CN=Yabukita,OU=Green,OU=Tea,DC=Japan";
274
275 static EXAMPLE_DN_QUOTED: &str = "\"CN=Yabukita,OU=Green,OU=Tea,DC=Japan\"";
276
277 fn example_simple_dn() -> SimpleDN {
279 SimpleDN {
280 rdns: vec![
281 SimpleRDN {
282 key: String::from("CN"),
283 value: String::from("Yabukita"),
284 },
285 SimpleRDN {
286 key: String::from("OU"),
287 value: String::from("Green"),
288 },
289 SimpleRDN {
290 key: String::from("OU"),
291 value: String::from("Tea"),
292 },
293 SimpleRDN {
294 key: String::from("DC"),
295 value: String::from("Japan"),
296 },
297 ],
298 }
299 }
300
301 #[test]
302 fn parse_simple_rdn_ok() {
303 let key = "CN";
304 let value = "Tea Drinker";
305
306 let unstructured = String::new() + key + "=" + value;
307
308 let rdn = simple_rdn_parser()
309 .parse(&unstructured)
310 .into_result()
311 .unwrap();
312
313 assert_eq!(key, rdn.key);
314 assert_eq!(value, rdn.value);
315 }
316
317 #[test]
318 fn parse_simple_rdn_fail() {
319 let key = "CN";
320 let value = "Tea Drinker";
321
322 let unstructured = String::new() + key + "=" + value + "+ANOTHER=5";
323
324 let parse_result = simple_rdn_parser().parse(&unstructured).into_result();
325
326 let errors = parse_result.unwrap_err();
327
328 println!("{errors:#?}");
329 }
330
331 #[test]
332 fn parse_sipmle_dn_ok() {
333 let parsed_dn = simple_dn_parser().parse(EXAMPLE_DN).into_result().unwrap();
334
335 assert_eq!(parsed_dn, example_simple_dn());
336 }
337
338 #[test]
339 fn parse_complex_dn() {
340 "CN=one+OTHER=two,OU=some,DC=thing".parse::<SimpleDN>()
341 .expect_err("Multivalued DN should be rejected.");
342 }
343
344 #[test]
345 fn parse_dn_escapes() -> anyhow::Result<()>{
346 let parsed = SimpleDN::from_str(r"CN=tea \+ milk \= milktea,OU=mixes,DC=odd\,domain")?;
347
348 let expected = SimpleDN { rdns: vec![
349 SimpleRDN {key: String::from("CN"), value: String::from("tea \\+ milk \\= milktea")},
350 SimpleRDN {key: String::from("OU"), value: String::from("mixes")},
351 SimpleRDN {key: String::from("DC"), value: String::from("odd\\,domain")},
352 ]};
353
354 assert_eq!(parsed, expected);
355
356 Ok(())
357 }
358
359 #[test]
360 fn dispaly_simple_dn() {
361 let displayed = example_simple_dn().to_string();
362 assert_eq!(displayed, EXAMPLE_DN);
363 }
364
365 #[derive(Debug, Deserialize, Serialize)]
367 #[serde(transparent)]
368 struct DnStruct {
369 pub dn: SimpleDN,
370 }
371
372 impl DnStruct {
373 fn example() -> Self {
374 DnStruct {
375 dn: example_simple_dn(),
376 }
377 }
378 }
379
380 #[test]
381 fn serialize() -> anyhow::Result<()> {
382 let serialized = serde_json::to_string(&DnStruct::example())?;
383 assert_eq!(serialized, EXAMPLE_DN_QUOTED);
384 Ok(())
385 }
386
387 #[test]
388 fn deserialize() -> anyhow::Result<()> {
389 let deserialized: DnStruct = serde_json::from_str(EXAMPLE_DN_QUOTED)?;
390 assert_eq!(deserialized.dn, DnStruct::example().dn);
391 Ok(())
392 }
393
394 #[test]
395 fn get() {
396 let example_dn = example_simple_dn();
397
398 assert_eq!(example_dn.get("OU"), Some("Green"));
399 assert_eq!(example_dn.get("CN"), Some("Yabukita"));
400 assert_eq!(example_dn.get("Nonsense"), None);
401 }
402
403 #[test]
404 fn get_type() {
405 assert_eq!(example_simple_dn().get_type(), "CN");
406 }
407
408 #[test]
409 fn get_parent() {
410 let parent = example_simple_dn().parent();
411 let correct_parent = SimpleDN {
412 rdns: vec![
413 SimpleRDN {
414 key: String::from("OU"),
415 value: String::from("Green"),
416 },
417 SimpleRDN {
418 key: String::from("OU"),
419 value: String::from("Tea"),
420 },
421 SimpleRDN {
422 key: String::from("DC"),
423 value: String::from("Japan"),
424 },
425 ],
426 };
427
428 assert_eq!(parent, Some(correct_parent.clone()));
429
430 let no_parents = SimpleDN {
431 rdns: vec![SimpleRDN {
432 key: String::from("DC"),
433 value: String::from("Tea"),
434 }],
435 };
436
437 assert_eq!(no_parents.parent(), None);
438 }
439
440 #[test]
441 fn get_starting_from() {
442 let example_dn = example_simple_dn();
443
444 let got = example_dn.get_starting_from("OU");
445 let correct = example_dn.parent();
446
447 assert!(got.is_some());
448 assert_eq!(got, correct);
449
450 let non_existent = example_dn.get_starting_from("Coffee");
451 assert_eq!(non_existent, None);
452 }
453
454 #[test]
455 fn get_type_starting_from() {
456 let example_dn = example_simple_dn();
457
458 let dn_type = example_dn.get_type();
459 let starting_from = example_dn.get_starting_from(dn_type);
460
461 assert_eq!(starting_from, Some(example_dn));
463 }
464
465 #[test]
466 fn partial_compare() {
467 let reflexivity = example_simple_dn().partial_cmp(&example_simple_dn());
468 assert_eq!(reflexivity, Some(Ordering::Equal));
469
470 let great = SimpleDN {
471 rdns: vec![SimpleRDN {
472 key: String::from("DC"),
473 value: String::from("Big"),
474 }],
475 };
476
477 let lesser = SimpleDN {
478 rdns: vec![
479 SimpleRDN {
480 key: String::from("OU"),
481 value: String::from("Medium"),
482 },
483 SimpleRDN {
484 key: String::from("DC"),
485 value: String::from("Big"),
486 },
487 ],
488 };
489
490 assert_eq!(great.partial_cmp(&lesser), Some(Ordering::Greater));
491 assert_eq!(lesser.partial_cmp(&great), Some(Ordering::Less));
492
493 let incomparable = SimpleDN {
495 rdns: vec![
496 SimpleRDN {
497 key: String::from("OU"),
498 value: String::from("Else"),
499 },
500 SimpleRDN {
501 key: String::from("DC"),
502 value: String::from("Big"),
503 },
504 ],
505 };
506
507 assert!(lesser.partial_cmp(&incomparable).is_none());
508 assert!(incomparable.partial_cmp(&lesser).is_none());
509 }
510
511 #[test]
512 fn test_common_ancestor() -> anyhow::Result<()> {
513 let left = SimpleDN::from_str("CN=puerh,OU=post-fermented,DC=tea")?;
514 let right = SimpleDN::from_str("CN=liu an,OU=post-fermented,DC=tea")?;
515 let correct_ancestor = SimpleDN::from_str("OU=post-fermented,DC=tea")?;
516
517 let found_ancestor = common_ancestor(&left, &right);
518
519 assert_eq!(found_ancestor, Some(correct_ancestor));
520
521 Ok(())
522 }
523}