1use std::collections::HashSet;
6
7#[cfg(feature = "chumsky")]
8use chumsky::{prelude::*, text::digits};
9use educe::Educe;
10use enum_as_inner::EnumAsInner;
11use oid::ObjectIdentifier;
12
13use itertools::Itertools;
14
15#[cfg(feature = "chumsky")]
16use lazy_static::lazy_static;
17
18use crate::basic::{KeyString, KeyStringOrOID, OIDWithLength};
19
20#[cfg(feature = "chumsky")]
21use crate::basic::{
22 keystring_or_oid_parser, keystring_parser, oid_parser, quoted_keystring_parser,
23};
24
25#[cfg(feature = "serde")]
26use serde::{Deserialize, Serialize};
27
28#[derive(Clone, Debug, EnumAsInner, Educe)]
30#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
31#[educe(PartialEq, Eq, Hash)]
32pub enum LDAPSchemaTagValue {
33 Standalone,
35 OID(#[educe(Hash(method = "crate::basic::hash_oid"))] ObjectIdentifier),
37 OIDWithLength(OIDWithLength),
39 String(String),
41 KeyString(KeyString),
43 QuotedKeyString(KeyString),
45 KeyStringOrOID(KeyStringOrOID),
47 Boolean(bool),
49 QuotedKeyStringList(Vec<KeyString>),
51 KeyStringOrOIDList(Vec<KeyStringOrOID>),
53}
54
55#[derive(PartialEq, Eq, Debug, Hash)]
57#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
58pub struct LDAPSchemaTag {
59 tag_name: String,
61 tag_value: LDAPSchemaTagValue,
63}
64
65#[cfg(feature = "chumsky")]
68#[derive(PartialEq, Eq, Debug, Hash)]
69#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
70pub enum LDAPSchemaTagType {
71 Standalone,
73 OID,
75 OIDWithLength,
77 String,
79 KeyString,
81 QuotedKeyString,
83 KeyStringOrOID,
85 Boolean,
87 QuotedKeyStringList,
89 KeyStringOrOIDList,
91}
92
93#[cfg(feature = "chumsky")]
95#[derive(PartialEq, Eq, Debug, Hash)]
96#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
97pub struct LDAPSchemaTagDescriptor {
98 pub tag_name: String,
100 pub tag_type: LDAPSchemaTagType,
102}
103
104#[cfg(feature = "chumsky")]
106pub fn ldap_schema_tag_value_parser(
107 tag_type: &LDAPSchemaTagType,
108) -> impl Parser<char, LDAPSchemaTagValue, Error = Simple<char>> {
109 match tag_type {
110 LDAPSchemaTagType::Standalone => empty()
111 .map(|_| LDAPSchemaTagValue::Standalone)
112 .labelled("no value")
113 .boxed(),
114 LDAPSchemaTagType::OID => oid_parser()
115 .map(LDAPSchemaTagValue::OID)
116 .labelled("OID")
117 .boxed(),
118 LDAPSchemaTagType::OIDWithLength => oid_parser()
119 .then(
120 digits(10)
121 .delimited_by(just('{'), just('}'))
122 .try_map(|x, span| {
123 x.parse().map_err(|e| {
124 Simple::custom(
125 span,
126 format!("Failed to convert parsed digits to integer: {}", e),
127 )
128 })
129 })
130 .or_not(),
131 )
132 .map(|(oid, len)| LDAPSchemaTagValue::OIDWithLength(OIDWithLength { oid, length: len }))
133 .labelled("OID with optional length")
134 .boxed(),
135 LDAPSchemaTagType::String => none_of("'")
136 .repeated()
137 .delimited_by(just('\''), just('\''))
138 .collect::<String>()
139 .map(LDAPSchemaTagValue::String)
140 .labelled("single-quoted string")
141 .boxed(),
142 LDAPSchemaTagType::KeyString => keystring_parser()
143 .map(LDAPSchemaTagValue::KeyString)
144 .labelled("keystring")
145 .boxed(),
146 LDAPSchemaTagType::QuotedKeyString => quoted_keystring_parser()
147 .map(LDAPSchemaTagValue::QuotedKeyString)
148 .labelled("quoted keystring")
149 .boxed(),
150 LDAPSchemaTagType::KeyStringOrOID => keystring_or_oid_parser()
151 .map(LDAPSchemaTagValue::KeyStringOrOID)
152 .labelled("keystring or OID")
153 .boxed(),
154 LDAPSchemaTagType::Boolean => just("TRUE")
155 .to(true)
156 .or(just("FALSE").to(false))
157 .delimited_by(just('\''), just('\''))
158 .map(LDAPSchemaTagValue::Boolean)
159 .labelled("single-quoted uppercase boolean")
160 .boxed(),
161 LDAPSchemaTagType::KeyStringOrOIDList => keystring_or_oid_parser()
162 .padded()
163 .separated_by(just('$'))
164 .delimited_by(just('('), just(')'))
165 .or(keystring_or_oid_parser().map(|x| vec![x]))
166 .map(LDAPSchemaTagValue::KeyStringOrOIDList)
167 .labelled("list of keystrings or OIDs separated by $")
168 .boxed(),
169 LDAPSchemaTagType::QuotedKeyStringList => quoted_keystring_parser()
170 .padded()
171 .repeated()
172 .delimited_by(just('('), just(')'))
173 .or(quoted_keystring_parser().map(|x| vec![x]))
174 .map(LDAPSchemaTagValue::QuotedKeyStringList)
175 .labelled("list of quoted keystrings separated by spaces")
176 .boxed(),
177 }
178}
179
180#[cfg(feature = "chumsky")]
182pub fn ldap_schema_tag_parser(
183 tag_descriptor: &LDAPSchemaTagDescriptor,
184) -> impl Parser<char, LDAPSchemaTag, Error = Simple<char>> + '_ {
185 just(tag_descriptor.tag_name.to_owned())
186 .padded()
187 .ignore_then(ldap_schema_tag_value_parser(&tag_descriptor.tag_type).padded())
188 .map(move |tag_value| LDAPSchemaTag {
189 tag_name: tag_descriptor.tag_name.to_string(),
190 tag_value,
191 })
192}
193
194#[cfg(feature = "chumsky")]
201pub fn ldap_schema_parser(
202 tag_descriptors: &[LDAPSchemaTagDescriptor],
203) -> impl Parser<char, (ObjectIdentifier, Vec<LDAPSchemaTag>), Error = Simple<char>> + '_ {
204 let (first, rest) = tag_descriptors
205 .split_first()
206 .expect("tag descriptors must have at least one element");
207 oid_parser()
208 .then(
209 rest.iter()
210 .fold(ldap_schema_tag_parser(first).boxed(), |p, td| {
211 p.or(ldap_schema_tag_parser(td)).boxed()
212 })
213 .padded()
214 .repeated(),
215 )
216 .padded()
217 .delimited_by(just('('), just(')'))
218}
219
220#[cfg(feature = "chumsky")]
222pub fn required_tag(
223 tag_name: &str,
224 span: &std::ops::Range<usize>,
225 tags: &[LDAPSchemaTag],
226) -> Result<LDAPSchemaTagValue, Simple<char>> {
227 tags.iter()
228 .find(|x| x.tag_name == tag_name)
229 .ok_or_else(|| {
230 Simple::custom(
231 span.clone(),
232 format!("No {} tag in parsed LDAP schema tag list", tag_name),
233 )
234 })
235 .map(|x| x.tag_value.to_owned())
236}
237
238#[cfg(feature = "chumsky")]
240pub fn optional_tag(tag_name: &str, tags: &[LDAPSchemaTag]) -> Option<LDAPSchemaTagValue> {
241 tags.iter()
242 .find(|x| x.tag_name == tag_name)
243 .map(|x| x.tag_value.to_owned())
244}
245
246#[derive(Clone, Educe)]
248#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
249#[educe(PartialEq, Eq, Hash)]
250pub struct LDAPSyntax {
251 #[educe(Hash(method = "crate::basic::hash_oid"))]
253 pub oid: ObjectIdentifier,
254 pub desc: String,
256 pub x_binary_transfer_required: bool,
258 pub x_not_human_readable: bool,
260}
261
262impl std::fmt::Debug for LDAPSyntax {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
264 let string_oid: String = self.oid.clone().into();
265 f.debug_struct("LDAPSyntax")
266 .field("oid", &string_oid)
267 .field("desc", &self.desc)
268 .field(
269 "x_binary_transfer_required",
270 &self.x_binary_transfer_required,
271 )
272 .field("x_not_human_readable", &self.x_not_human_readable)
273 .finish()
274 }
275}
276
277#[cfg(feature = "chumsky")]
281pub fn ldap_syntax_parser() -> impl Parser<char, LDAPSyntax, Error = Simple<char>> {
282 lazy_static! {
283 static ref LDAP_SYNTAX_TAGS: Vec<LDAPSchemaTagDescriptor> = vec![
284 LDAPSchemaTagDescriptor {
285 tag_name: "DESC".to_string(),
286 tag_type: LDAPSchemaTagType::String
287 },
288 LDAPSchemaTagDescriptor {
289 tag_name: "X-BINARY-TRANSFER-REQUIRED".to_string(),
290 tag_type: LDAPSchemaTagType::Boolean
291 },
292 LDAPSchemaTagDescriptor {
293 tag_name: "X-NOT-HUMAN-READABLE".to_string(),
294 tag_type: LDAPSchemaTagType::Boolean
295 },
296 ];
297 }
298 ldap_schema_parser(&LDAP_SYNTAX_TAGS).try_map(|(oid, tags), span| {
299 Ok(LDAPSyntax {
300 oid,
301 desc: required_tag("DESC", &span, &tags)?
302 .as_string()
303 .unwrap()
304 .to_string(),
305 x_binary_transfer_required: *optional_tag("X-BINARY-TRANSFER-REQUIRED", &tags)
306 .unwrap_or(LDAPSchemaTagValue::Boolean(false))
307 .as_boolean()
308 .unwrap(),
309 x_not_human_readable: *optional_tag("X-NOT-HUMAN-READABLE", &tags)
310 .unwrap_or(LDAPSchemaTagValue::Boolean(false))
311 .as_boolean()
312 .unwrap(),
313 })
314 })
315}
316
317#[derive(Clone, Educe)]
321#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
322#[educe(PartialEq, Eq, Hash)]
323pub struct MatchingRule {
324 #[educe(Hash(method = "crate::basic::hash_oid"))]
326 pub oid: ObjectIdentifier,
327 pub name: Vec<KeyString>,
329 pub syntax: OIDWithLength,
331}
332
333impl std::fmt::Debug for MatchingRule {
334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
335 let string_oid: String = self.oid.clone().into();
336 f.debug_struct("MatchingRule")
337 .field("oid", &string_oid)
338 .field("name", &self.name)
339 .field("syntax", &self.syntax)
340 .finish()
341 }
342}
343
344#[cfg(feature = "chumsky")]
346pub fn matching_rule_parser() -> impl Parser<char, MatchingRule, Error = Simple<char>> {
347 lazy_static! {
348 static ref MATCHING_RULE_TAGS: Vec<LDAPSchemaTagDescriptor> = vec![
349 LDAPSchemaTagDescriptor {
350 tag_name: "NAME".to_string(),
351 tag_type: LDAPSchemaTagType::QuotedKeyStringList
352 },
353 LDAPSchemaTagDescriptor {
354 tag_name: "SYNTAX".to_string(),
355 tag_type: LDAPSchemaTagType::OIDWithLength
356 },
357 ];
358 }
359 ldap_schema_parser(&MATCHING_RULE_TAGS).try_map(|(oid, tags), span| {
360 Ok(MatchingRule {
361 oid,
362 name: required_tag("NAME", &span, &tags)?
363 .as_quoted_key_string_list()
364 .unwrap()
365 .to_vec(),
366 syntax: required_tag("SYNTAX", &span, &tags)?
367 .as_oid_with_length()
368 .unwrap()
369 .to_owned(),
370 })
371 })
372}
373
374#[derive(Clone, Educe)]
378#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
379#[educe(PartialEq, Eq, Hash)]
380pub struct MatchingRuleUse {
381 #[educe(Hash(method = "crate::basic::hash_oid"))]
383 pub oid: ObjectIdentifier,
384 pub name: Vec<KeyString>,
386 pub applies: Vec<KeyStringOrOID>,
388}
389
390impl std::fmt::Debug for MatchingRuleUse {
391 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
392 let string_oid: String = self.oid.clone().into();
393 f.debug_struct("MatchingRuleUse")
394 .field("oid", &string_oid)
395 .field("name", &self.name)
396 .field("applies", &self.applies)
397 .finish()
398 }
399}
400
401#[cfg(feature = "chumsky")]
403pub fn matching_rule_use_parser() -> impl Parser<char, MatchingRuleUse, Error = Simple<char>> {
404 lazy_static! {
405 static ref MATCHING_RULE_USE_TAGS: Vec<LDAPSchemaTagDescriptor> = vec![
406 LDAPSchemaTagDescriptor {
407 tag_name: "NAME".to_string(),
408 tag_type: LDAPSchemaTagType::QuotedKeyStringList
409 },
410 LDAPSchemaTagDescriptor {
411 tag_name: "APPLIES".to_string(),
412 tag_type: LDAPSchemaTagType::KeyStringOrOIDList
413 },
414 ];
415 }
416 ldap_schema_parser(&MATCHING_RULE_USE_TAGS).try_map(|(oid, tags), span| {
417 Ok(MatchingRuleUse {
418 oid,
419 name: required_tag("NAME", &span, &tags)?
420 .as_quoted_key_string_list()
421 .unwrap()
422 .to_vec(),
423 applies: required_tag("APPLIES", &span, &tags)?
424 .as_key_string_or_oid_list()
425 .unwrap()
426 .to_vec(),
427 })
428 })
429}
430
431#[derive(Clone, Educe)]
435#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
436#[educe(PartialEq, Eq, Hash)]
437pub struct AttributeType {
438 #[educe(Hash(method = "crate::basic::hash_oid"))]
440 pub oid: ObjectIdentifier,
441 pub name: Vec<KeyString>,
443 pub sup: Option<KeyString>,
445 pub desc: Option<String>,
447 pub syntax: Option<OIDWithLength>,
449 pub single_value: bool,
451 pub equality: Option<KeyString>,
453 pub substr: Option<KeyString>,
455 pub ordering: Option<KeyString>,
457 pub no_user_modification: bool,
460 pub usage: Option<KeyString>,
467 pub collective: bool,
471 pub obsolete: bool,
473 pub x_ordered: Option<KeyString>,
480}
481
482impl std::fmt::Debug for AttributeType {
483 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
484 let string_oid: String = self.oid.clone().into();
485 f.debug_struct("AttributeType")
486 .field("oid", &string_oid)
487 .field("name", &self.name)
488 .field("sup", &self.sup)
489 .field("desc", &self.desc)
490 .field("syntax", &self.syntax)
491 .field("single_value", &self.single_value)
492 .field("equality", &self.equality)
493 .field("substr", &self.substr)
494 .field("ordering", &self.ordering)
495 .field("no_user_modification", &self.no_user_modification)
496 .field("usage", &self.usage)
497 .field("collective", &self.collective)
498 .field("obsolete", &self.obsolete)
499 .field("x_ordered", &self.x_ordered)
500 .finish()
501 }
502}
503
504#[cfg(feature = "chumsky")]
506pub fn attribute_type_parser() -> impl Parser<char, AttributeType, Error = Simple<char>> {
507 lazy_static! {
508 static ref ATTRIBUTE_TYPE_TAGS: Vec<LDAPSchemaTagDescriptor> = vec![
509 LDAPSchemaTagDescriptor {
510 tag_name: "NAME".to_string(),
511 tag_type: LDAPSchemaTagType::QuotedKeyStringList
512 },
513 LDAPSchemaTagDescriptor {
514 tag_name: "SUP".to_string(),
515 tag_type: LDAPSchemaTagType::KeyString
516 },
517 LDAPSchemaTagDescriptor {
518 tag_name: "DESC".to_string(),
519 tag_type: LDAPSchemaTagType::String
520 },
521 LDAPSchemaTagDescriptor {
522 tag_name: "SYNTAX".to_string(),
523 tag_type: LDAPSchemaTagType::OIDWithLength
524 },
525 LDAPSchemaTagDescriptor {
526 tag_name: "EQUALITY".to_string(),
527 tag_type: LDAPSchemaTagType::KeyString
528 },
529 LDAPSchemaTagDescriptor {
530 tag_name: "SUBSTR".to_string(),
531 tag_type: LDAPSchemaTagType::KeyString
532 },
533 LDAPSchemaTagDescriptor {
534 tag_name: "ORDERING".to_string(),
535 tag_type: LDAPSchemaTagType::KeyString
536 },
537 LDAPSchemaTagDescriptor {
538 tag_name: "SINGLE-VALUE".to_string(),
539 tag_type: LDAPSchemaTagType::Standalone
540 },
541 LDAPSchemaTagDescriptor {
542 tag_name: "NO-USER-MODIFICATION".to_string(),
543 tag_type: LDAPSchemaTagType::Standalone
544 },
545 LDAPSchemaTagDescriptor {
546 tag_name: "USAGE".to_string(),
547 tag_type: LDAPSchemaTagType::KeyString
548 },
549 LDAPSchemaTagDescriptor {
550 tag_name: "COLLECTIVE".to_string(),
551 tag_type: LDAPSchemaTagType::Standalone
552 },
553 LDAPSchemaTagDescriptor {
554 tag_name: "OBSOLETE".to_string(),
555 tag_type: LDAPSchemaTagType::Standalone
556 },
557 LDAPSchemaTagDescriptor {
558 tag_name: "X-ORDERED".to_string(),
559 tag_type: LDAPSchemaTagType::QuotedKeyString
560 },
561 ];
562 }
563 ldap_schema_parser(&ATTRIBUTE_TYPE_TAGS).try_map(|(oid, tags), span| {
564 Ok(AttributeType {
565 oid,
566 name: required_tag("NAME", &span, &tags)?
567 .as_quoted_key_string_list()
568 .unwrap()
569 .to_vec(),
570 sup: optional_tag("SUP", &tags).map(|s| s.as_key_string().unwrap().to_owned()),
571 desc: optional_tag("DESC", &tags).map(|v| v.as_string().unwrap().to_string()),
572 syntax: optional_tag("SYNTAX", &tags)
573 .map(|v| v.as_oid_with_length().unwrap().to_owned()),
574 single_value: optional_tag("SINGLE-VALUE", &tags).is_some(),
575 equality: optional_tag("EQUALITY", &tags)
576 .map(|s| s.as_key_string().unwrap().to_owned()),
577 substr: optional_tag("SUBSTR", &tags).map(|s| s.as_key_string().unwrap().to_owned()),
578 ordering: optional_tag("ORDERING", &tags)
579 .map(|s| s.as_key_string().unwrap().to_owned()),
580 no_user_modification: optional_tag("NO-USER-MODIFICATION", &tags).is_some(),
581 usage: optional_tag("USAGE", &tags).map(|s| s.as_key_string().unwrap().to_owned()),
582 collective: optional_tag("COLLECTIVE", &tags).is_some(),
583 obsolete: optional_tag("OBSOLETE", &tags).is_some(),
584 x_ordered: optional_tag("X-ORDERED", &tags)
585 .map(|s| s.as_quoted_key_string().unwrap().to_owned()),
586 })
587 })
588}
589
590#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner, Hash)]
592#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
593pub enum ObjectClassType {
594 Abstract,
597 Structural,
601 Auxiliary,
604}
605
606#[derive(Clone, Educe)]
608#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
609#[educe(PartialEq, Eq, Hash)]
610pub struct ObjectClass {
611 #[educe(Hash(method = "crate::basic::hash_oid"))]
613 pub oid: ObjectIdentifier,
614 pub name: Vec<KeyString>,
616 pub sup: Vec<KeyStringOrOID>,
618 pub desc: Option<String>,
620 pub object_class_type: ObjectClassType,
622 pub must: Vec<KeyStringOrOID>,
624 pub may: Vec<KeyStringOrOID>,
627 pub obsolete: bool,
629}
630
631impl std::fmt::Debug for ObjectClass {
632 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
633 let string_oid: String = self.oid.clone().into();
634 f.debug_struct("ObjectClass")
635 .field("oid", &string_oid)
636 .field("name", &self.name)
637 .field("sup", &self.sup)
638 .field("desc", &self.desc)
639 .field("object_class_type", &self.object_class_type)
640 .field("must", &self.must)
641 .field("may", &self.may)
642 .field("obsolete", &self.obsolete)
643 .finish()
644 }
645}
646
647#[cfg(feature = "chumsky")]
649pub fn object_class_parser() -> impl Parser<char, ObjectClass, Error = Simple<char>> {
650 lazy_static! {
651 static ref OBJECT_CLASS_TAGS: Vec<LDAPSchemaTagDescriptor> = vec![
652 LDAPSchemaTagDescriptor {
653 tag_name: "NAME".to_string(),
654 tag_type: LDAPSchemaTagType::QuotedKeyStringList
655 },
656 LDAPSchemaTagDescriptor {
657 tag_name: "SUP".to_string(),
658 tag_type: LDAPSchemaTagType::KeyStringOrOIDList
659 },
660 LDAPSchemaTagDescriptor {
661 tag_name: "DESC".to_string(),
662 tag_type: LDAPSchemaTagType::String
663 },
664 LDAPSchemaTagDescriptor {
665 tag_name: "ABSTRACT".to_string(),
666 tag_type: LDAPSchemaTagType::Standalone
667 },
668 LDAPSchemaTagDescriptor {
669 tag_name: "STRUCTURAL".to_string(),
670 tag_type: LDAPSchemaTagType::Standalone
671 },
672 LDAPSchemaTagDescriptor {
673 tag_name: "AUXILIARY".to_string(),
674 tag_type: LDAPSchemaTagType::Standalone
675 },
676 LDAPSchemaTagDescriptor {
677 tag_name: "MUST".to_string(),
678 tag_type: LDAPSchemaTagType::KeyStringOrOIDList
679 },
680 LDAPSchemaTagDescriptor {
681 tag_name: "MAY".to_string(),
682 tag_type: LDAPSchemaTagType::KeyStringOrOIDList
683 },
684 LDAPSchemaTagDescriptor {
685 tag_name: "OBSOLETE".to_string(),
686 tag_type: LDAPSchemaTagType::Standalone
687 },
688 ];
689 }
690 ldap_schema_parser(&OBJECT_CLASS_TAGS).try_map(|(oid, tags), span| {
691 Ok(ObjectClass {
692 oid,
693 name: required_tag("NAME", &span, &tags)?
694 .as_quoted_key_string_list()
695 .unwrap()
696 .to_vec(),
697 sup: optional_tag("SUP", &tags)
698 .map(|s| s.as_key_string_or_oid_list().unwrap().to_owned())
699 .unwrap_or_default(),
700 desc: optional_tag("DESC", &tags).map(|v| v.as_string().unwrap().to_string()),
701 object_class_type: optional_tag("ABSTRACT", &tags)
702 .map(|_| ObjectClassType::Abstract)
703 .or_else(|| optional_tag("STRUCTURAL", &tags).map(|_| ObjectClassType::Structural))
704 .or_else(|| optional_tag("AUXILIARY", &tags).map(|_| ObjectClassType::Auxiliary))
705 .unwrap_or(ObjectClassType::Structural),
706 must: optional_tag("MUST", &tags)
707 .map(|v| v.as_key_string_or_oid_list().unwrap().to_vec())
708 .unwrap_or_default(),
709 may: optional_tag("MAY", &tags)
710 .map(|v| v.as_key_string_or_oid_list().unwrap().to_vec())
711 .unwrap_or_default(),
712 obsolete: optional_tag("OBSOLETE", &tags).is_some(),
713 })
714 })
715}
716
717#[derive(Debug, Clone, Hash)]
719#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
720pub struct LDAPSchema {
721 pub ldap_syntaxes: Vec<LDAPSyntax>,
723 pub matching_rules: Vec<MatchingRule>,
725 pub matching_rule_use: Vec<MatchingRuleUse>,
727 pub attribute_types: Vec<AttributeType>,
729 pub object_classes: Vec<ObjectClass>,
731 }
736
737impl LDAPSchema {
738 pub fn allowed_attributes(
740 &self,
741 id: impl TryInto<KeyStringOrOID>,
742 ) -> Option<HashSet<&AttributeType>> {
743 if let Some(object_class) = self.find_object_class(id) {
744 let mut result = HashSet::new();
745 for attribute_name in object_class.must.iter().chain(object_class.may.iter()) {
746 if let Some(attribute) = self.find_attribute_type(attribute_name) {
747 result.insert(attribute);
748 }
749 }
750 for sup in &object_class.sup {
751 if let Some(allowed_attributes) = self.allowed_attributes(sup) {
752 result.extend(allowed_attributes);
753 }
754 }
755 Some(result)
756 } else {
757 None
758 }
759 }
760
761 pub fn required_attributes(
763 &self,
764 id: impl TryInto<KeyStringOrOID>,
765 ) -> Option<HashSet<&AttributeType>> {
766 if let Some(object_class) = self.find_object_class(id) {
767 let mut result = HashSet::new();
768 for attribute_name in object_class.must.iter() {
769 if let Some(attribute) = self.find_attribute_type(attribute_name) {
770 result.insert(attribute);
771 }
772 }
773 for sup in &object_class.sup {
774 if let Some(required_attributes) = self.required_attributes(sup) {
775 result.extend(required_attributes);
776 }
777 }
778 Some(result)
779 } else {
780 None
781 }
782 }
783
784 pub fn find_object_class(&self, id: impl TryInto<KeyStringOrOID>) -> Option<&ObjectClass> {
786 let id: Result<KeyStringOrOID, _> = id.try_into();
787 match id {
788 Ok(id) => {
789 let match_fn: Box<dyn FnMut(&&ObjectClass) -> bool> = match id {
790 KeyStringOrOID::OID(oid) => Box::new(move |at: &&ObjectClass| at.oid == oid),
791 KeyStringOrOID::KeyString(s) => Box::new(move |at: &&ObjectClass| {
792 at.name
793 .iter()
794 .map(|n| n.to_lowercase())
795 .contains(&s.to_lowercase())
796 }),
797 };
798 self.object_classes.iter().find(match_fn)
799 }
800 Err(_) => None,
801 }
802 }
803
804 pub fn find_object_class_property<'a, R>(
808 &'a self,
809 id: impl TryInto<KeyStringOrOID>,
810 f: fn(&'a ObjectClass) -> Option<&'a R>,
811 ) -> Option<&'a R> {
812 let object_class = self.find_object_class(id);
813 if let Some(object_class) = object_class {
814 if let Some(r) = f(object_class) {
815 Some(r)
816 } else {
817 let ks_or_oids = &object_class.sup;
818 for ks_or_oid in ks_or_oids {
819 if let Some(r) = self.find_object_class_property(ks_or_oid, f) {
820 return Some(r);
821 }
822 }
823 None
824 }
825 } else {
826 None
827 }
828 }
829
830 pub fn find_attribute_type(&self, id: impl TryInto<KeyStringOrOID>) -> Option<&AttributeType> {
832 let id: Result<KeyStringOrOID, _> = id.try_into();
833 match id {
834 Ok(id) => {
835 let match_fn: Box<dyn FnMut(&&AttributeType) -> bool> = match id {
836 KeyStringOrOID::OID(oid) => Box::new(move |at: &&AttributeType| at.oid == oid),
837 KeyStringOrOID::KeyString(s) => Box::new(move |at: &&AttributeType| {
838 at.name
839 .iter()
840 .map(|n| n.to_lowercase())
841 .contains(&s.to_lowercase())
842 }),
843 };
844 self.attribute_types.iter().find(match_fn)
845 }
846 Err(_) => None,
847 }
848 }
849
850 pub fn find_attribute_type_property<'a, R>(
854 &'a self,
855 id: impl TryInto<KeyStringOrOID>,
856 f: fn(&'a AttributeType) -> Option<&'a R>,
857 ) -> Option<&'a R> {
858 let attribute_type = self.find_attribute_type(id);
859 if let Some(attribute_type) = attribute_type {
860 if let Some(r) = f(attribute_type) {
861 Some(r)
862 } else if let Some(sup @ KeyString(_)) = &attribute_type.sup {
863 self.find_attribute_type_property(KeyStringOrOID::KeyString(sup.to_owned()), f)
864 } else {
865 None
866 }
867 } else {
868 None
869 }
870 }
871
872 #[cfg(feature = "chumsky")]
874 pub fn find_ldap_syntax(&self, id: impl TryInto<ObjectIdentifier>) -> Option<&LDAPSyntax> {
875 let id: Result<ObjectIdentifier, _> = id.try_into();
876 match id {
877 Ok(id) => self
878 .ldap_syntaxes
879 .iter()
880 .find(move |ls: &&LDAPSyntax| ls.oid == id),
881 Err(_) => None,
882 }
883 }
884
885 #[cfg(feature = "chumsky")]
887 pub fn find_matching_rule(&self, id: impl TryInto<ObjectIdentifier>) -> Option<&MatchingRule> {
888 let id: Result<ObjectIdentifier, _> = id.try_into();
889 match id {
890 Ok(id) => self
891 .matching_rules
892 .iter()
893 .find(move |ls: &&MatchingRule| ls.oid == id),
894 Err(_) => None,
895 }
896 }
897
898 #[cfg(feature = "chumsky")]
900 pub fn find_matching_rule_use(
901 &self,
902 id: impl TryInto<ObjectIdentifier>,
903 ) -> Option<&MatchingRuleUse> {
904 let id: Result<ObjectIdentifier, _> = id.try_into();
905 match id {
906 Ok(id) => self
907 .matching_rule_use
908 .iter()
909 .find(move |ls: &&MatchingRuleUse| ls.oid == id),
910 Err(_) => None,
911 }
912 }
913}
914
915#[cfg(test)]
916mod test {
917 #[cfg(feature = "chumsky")]
918 use super::*;
919
920 #[cfg(feature = "chumsky")]
921 #[test]
922 fn test_parse_ldap_syntax() {
923 assert!(ldap_syntax_parser().parse("( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' X-BINARY-TRANSFER-REQUIRED 'TRUE' X-NOT-HUMAN-READABLE 'TRUE' )").is_ok());
924 }
925
926 #[cfg(feature = "chumsky")]
927 #[test]
928 fn test_parse_ldap_syntax_value1() {
929 assert_eq!(ldap_syntax_parser().parse("( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' X-BINARY-TRANSFER-REQUIRED 'TRUE' X-NOT-HUMAN-READABLE 'TRUE' )"),
930 Ok(LDAPSyntax { oid: "1.3.6.1.4.1.1466.115.121.1.8".to_string().try_into().unwrap(),
931 desc: "Certificate".to_string(),
932 x_binary_transfer_required: true,
933 x_not_human_readable: true,
934 }
935 ));
936 }
937
938 #[cfg(feature = "chumsky")]
939 #[test]
940 fn test_parse_ldap_syntax_value2() {
941 assert_eq!(ldap_syntax_parser().parse("( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' X-NOT-HUMAN-READABLE 'TRUE' X-BINARY-TRANSFER-REQUIRED 'TRUE' )"),
942 Ok(LDAPSyntax { oid: "1.3.6.1.4.1.1466.115.121.1.8".to_string().try_into().unwrap(),
943 desc: "Certificate".to_string(),
944 x_binary_transfer_required: true,
945 x_not_human_readable: true,
946 }
947 ));
948 }
949
950 #[cfg(feature = "chumsky")]
951 #[test]
952 fn test_parse_ldap_syntax_value3() {
953 assert_eq!(ldap_syntax_parser().parse("( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' X-BINARY-TRANSFER-REQUIRED 'TRUE' )"),
954 Ok(LDAPSyntax { oid: "1.3.6.1.4.1.1466.115.121.1.8".to_string().try_into().unwrap(),
955 desc: "Certificate".to_string(),
956 x_binary_transfer_required: true,
957 x_not_human_readable: false,
958 }
959 ));
960 }
961
962 #[cfg(feature = "chumsky")]
963 #[test]
964 fn test_parse_ldap_syntax_value4() {
965 assert_eq!(
966 ldap_syntax_parser().parse(
967 "( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' X-NOT-HUMAN-READABLE 'TRUE' )"
968 ),
969 Ok(LDAPSyntax {
970 oid: "1.3.6.1.4.1.1466.115.121.1.8"
971 .to_string()
972 .try_into()
973 .unwrap(),
974 desc: "Certificate".to_string(),
975 x_binary_transfer_required: false,
976 x_not_human_readable: true,
977 })
978 );
979 }
980
981 #[cfg(feature = "chumsky")]
982 #[test]
983 fn test_parse_ldap_syntax_value5() {
984 assert_eq!(
985 ldap_syntax_parser().parse("( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' )"),
986 Ok(LDAPSyntax {
987 oid: "1.3.6.1.4.1.1466.115.121.1.8"
988 .to_string()
989 .try_into()
990 .unwrap(),
991 desc: "Certificate".to_string(),
992 x_binary_transfer_required: false,
993 x_not_human_readable: false,
994 })
995 );
996 }
997
998 #[cfg(feature = "chumsky")]
999 #[test]
1000 fn test_parse_ldap_syntax_desc_required() {
1001 assert!(ldap_syntax_parser()
1002 .parse("( 1.3.6.1.4.1.1466.115.121.1.8 )")
1003 .is_err());
1004 }
1005
1006 #[cfg(feature = "chumsky")]
1007 #[test]
1008 fn test_parse_matching_rule() {
1009 assert!(matching_rule_parser()
1010 .parse("( 1.3.6.1.1.16.3 NAME 'UUIDOrderingMatch' SYNTAX 1.3.6.1.1.16.1 )")
1011 .is_ok());
1012 }
1013
1014 #[cfg(feature = "chumsky")]
1015 #[test]
1016 fn test_parse_matching_rule_value() {
1017 assert_eq!(
1018 matching_rule_parser()
1019 .parse("( 1.3.6.1.1.16.3 NAME 'UUIDOrderingMatch' SYNTAX 1.3.6.1.1.16.1 )"),
1020 Ok(MatchingRule {
1021 oid: "1.3.6.1.1.16.3".to_string().try_into().unwrap(),
1022 name: vec![KeyString("UUIDOrderingMatch".to_string())],
1023 syntax: OIDWithLength {
1024 oid: "1.3.6.1.1.16.1".to_string().try_into().unwrap(),
1025 length: None
1026 },
1027 })
1028 );
1029 }
1030
1031 #[cfg(feature = "chumsky")]
1032 #[test]
1033 fn test_parse_matching_rule_uses() {
1034 assert!(matching_rule_use_parser().parse("( 2.5.13.11 NAME 'caseIgnoreListMatch' APPLIES ( postalAddress $ registeredAddress $ homePostalAddress ) )").is_ok());
1035 }
1036
1037 #[cfg(feature = "chumsky")]
1038 #[test]
1039 fn test_parse_matching_rule_uses_value() {
1040 assert_eq!(matching_rule_use_parser().parse("( 2.5.13.11 NAME 'caseIgnoreListMatch' APPLIES ( postalAddress $ registeredAddress $ homePostalAddress ) )"),
1041 Ok(MatchingRuleUse { oid: "2.5.13.11".to_string().try_into().unwrap(),
1042 name: vec![KeyString("caseIgnoreListMatch".to_string())],
1043 applies: vec![KeyStringOrOID::KeyString(KeyString("postalAddress".to_string())),
1044 KeyStringOrOID::KeyString(KeyString("registeredAddress".to_string())),
1045 KeyStringOrOID::KeyString(KeyString("homePostalAddress".to_string()))
1046 ],
1047 })
1048 );
1049 }
1050
1051 #[cfg(feature = "chumsky")]
1052 #[test]
1053 fn test_parse_matching_rule_uses_single_applies_value() {
1054 assert_eq!(
1055 matching_rule_use_parser()
1056 .parse("( 2.5.13.11 NAME 'caseIgnoreListMatch' APPLIES postalAddress )"),
1057 Ok(MatchingRuleUse {
1058 oid: "2.5.13.11".to_string().try_into().unwrap(),
1059 name: vec![KeyString("caseIgnoreListMatch".to_string())],
1060 applies: vec![KeyStringOrOID::KeyString(KeyString(
1061 "postalAddress".to_string()
1062 ))],
1063 })
1064 );
1065 }
1066}