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