use std::collections::HashSet;
#[cfg(feature = "chumsky")]
use chumsky::{prelude::*, text::digits};
use educe::Educe;
use enum_as_inner::EnumAsInner;
use oid::ObjectIdentifier;
#[cfg(feature = "chumsky")]
use itertools::Itertools;
#[cfg(feature = "chumsky")]
use lazy_static::lazy_static;
use crate::basic::{KeyString, KeyStringOrOID, OIDWithLength};
#[cfg(feature = "chumsky")]
use crate::basic::{
keystring_or_oid_parser, keystring_parser, oid_parser, quoted_keystring_parser,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, EnumAsInner, Educe)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[educe(PartialEq, Eq, Hash)]
pub enum LDAPSchemaTagValue {
Standalone,
OID(#[educe(Hash(method = "crate::basic::hash_oid"))] ObjectIdentifier),
OIDWithLength(OIDWithLength),
String(String),
KeyString(KeyString),
QuotedKeyString(KeyString),
KeyStringOrOID(KeyStringOrOID),
Boolean(bool),
QuotedKeyStringList(Vec<KeyString>),
KeyStringOrOIDList(Vec<KeyStringOrOID>),
}
#[derive(PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LDAPSchemaTag {
tag_name: String,
tag_value: LDAPSchemaTagValue,
}
#[cfg(feature = "chumsky")]
#[derive(PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LDAPSchemaTagType {
Standalone,
OID,
OIDWithLength,
String,
KeyString,
QuotedKeyString,
KeyStringOrOID,
Boolean,
QuotedKeyStringList,
KeyStringOrOIDList,
}
#[cfg(feature = "chumsky")]
#[derive(PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LDAPSchemaTagDescriptor {
pub tag_name: String,
pub tag_type: LDAPSchemaTagType,
}
#[cfg(feature = "chumsky")]
pub fn ldap_schema_tag_value_parser(
tag_type: &LDAPSchemaTagType,
) -> impl Parser<char, LDAPSchemaTagValue, Error = Simple<char>> {
match tag_type {
LDAPSchemaTagType::Standalone => empty()
.map(|_| LDAPSchemaTagValue::Standalone)
.labelled("no value")
.boxed(),
LDAPSchemaTagType::OID => oid_parser()
.map(LDAPSchemaTagValue::OID)
.labelled("OID")
.boxed(),
LDAPSchemaTagType::OIDWithLength => oid_parser()
.then(
digits(10)
.delimited_by(just('{'), just('}'))
.try_map(|x, span| {
x.parse().map_err(|e| {
Simple::custom(
span,
format!("Failed to convert parsed digits to integer: {}", e),
)
})
})
.or_not(),
)
.map(|(oid, len)| LDAPSchemaTagValue::OIDWithLength(OIDWithLength { oid, length: len }))
.labelled("OID with optional length")
.boxed(),
LDAPSchemaTagType::String => none_of("'")
.repeated()
.delimited_by(just('\''), just('\''))
.collect::<String>()
.map(LDAPSchemaTagValue::String)
.labelled("single-quoted string")
.boxed(),
LDAPSchemaTagType::KeyString => keystring_parser()
.map(LDAPSchemaTagValue::KeyString)
.labelled("keystring")
.boxed(),
LDAPSchemaTagType::QuotedKeyString => quoted_keystring_parser()
.map(LDAPSchemaTagValue::QuotedKeyString)
.labelled("quoted keystring")
.boxed(),
LDAPSchemaTagType::KeyStringOrOID => keystring_or_oid_parser()
.map(LDAPSchemaTagValue::KeyStringOrOID)
.labelled("keystring or OID")
.boxed(),
LDAPSchemaTagType::Boolean => just("TRUE")
.to(true)
.or(just("FALSE").to(false))
.delimited_by(just('\''), just('\''))
.map(LDAPSchemaTagValue::Boolean)
.labelled("single-quoted uppercase boolean")
.boxed(),
LDAPSchemaTagType::KeyStringOrOIDList => keystring_or_oid_parser()
.padded()
.separated_by(just('$'))
.delimited_by(just('('), just(')'))
.or(keystring_or_oid_parser().map(|x| vec![x]))
.map(LDAPSchemaTagValue::KeyStringOrOIDList)
.labelled("list of keystrings or OIDs separated by $")
.boxed(),
LDAPSchemaTagType::QuotedKeyStringList => quoted_keystring_parser()
.padded()
.repeated()
.delimited_by(just('('), just(')'))
.or(quoted_keystring_parser().map(|x| vec![x]))
.map(LDAPSchemaTagValue::QuotedKeyStringList)
.labelled("list of quoted keystrings separated by spaces")
.boxed(),
}
}
#[cfg(feature = "chumsky")]
pub fn ldap_schema_tag_parser(
tag_descriptor: &LDAPSchemaTagDescriptor,
) -> impl Parser<char, LDAPSchemaTag, Error = Simple<char>> + '_ {
just(tag_descriptor.tag_name.to_owned())
.padded()
.ignore_then(ldap_schema_tag_value_parser(&tag_descriptor.tag_type).padded())
.map(move |tag_value| LDAPSchemaTag {
tag_name: tag_descriptor.tag_name.to_string(),
tag_value,
})
}
#[cfg(feature = "chumsky")]
pub fn ldap_schema_parser(
tag_descriptors: &[LDAPSchemaTagDescriptor],
) -> impl Parser<char, (ObjectIdentifier, Vec<LDAPSchemaTag>), Error = Simple<char>> + '_ {
let (first, rest) = tag_descriptors
.split_first()
.expect("tag descriptors must have at least one element");
oid_parser()
.then(
rest.iter()
.fold(ldap_schema_tag_parser(first).boxed(), |p, td| {
p.or(ldap_schema_tag_parser(td)).boxed()
})
.padded()
.repeated(),
)
.padded()
.delimited_by(just('('), just(')'))
}
#[cfg(feature = "chumsky")]
pub fn required_tag(
tag_name: &str,
span: &std::ops::Range<usize>,
tags: &[LDAPSchemaTag],
) -> Result<LDAPSchemaTagValue, Simple<char>> {
tags.iter()
.find(|x| x.tag_name == tag_name)
.ok_or_else(|| {
Simple::custom(
span.clone(),
format!("No {} tag in parsed LDAP schema tag list", tag_name),
)
})
.map(|x| x.tag_value.to_owned())
}
#[cfg(feature = "chumsky")]
pub fn optional_tag(tag_name: &str, tags: &[LDAPSchemaTag]) -> Option<LDAPSchemaTagValue> {
tags.iter()
.find(|x| x.tag_name == tag_name)
.map(|x| x.tag_value.to_owned())
}
#[derive(Clone, Educe)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[educe(PartialEq, Eq, Hash)]
pub struct LDAPSyntax {
#[educe(Hash(method = "crate::basic::hash_oid"))]
pub oid: ObjectIdentifier,
pub desc: String,
pub x_binary_transfer_required: bool,
pub x_not_human_readable: bool,
}
impl std::fmt::Debug for LDAPSyntax {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let string_oid: String = self.oid.clone().into();
f.debug_struct("LDAPSyntax")
.field("oid", &string_oid)
.field("desc", &self.desc)
.field(
"x_binary_transfer_required",
&self.x_binary_transfer_required,
)
.field("x_not_human_readable", &self.x_not_human_readable)
.finish()
}
}
#[cfg(feature = "chumsky")]
pub fn ldap_syntax_parser() -> impl Parser<char, LDAPSyntax, Error = Simple<char>> {
lazy_static! {
static ref LDAP_SYNTAX_TAGS: Vec<LDAPSchemaTagDescriptor> = vec![
LDAPSchemaTagDescriptor {
tag_name: "DESC".to_string(),
tag_type: LDAPSchemaTagType::String
},
LDAPSchemaTagDescriptor {
tag_name: "X-BINARY-TRANSFER-REQUIRED".to_string(),
tag_type: LDAPSchemaTagType::Boolean
},
LDAPSchemaTagDescriptor {
tag_name: "X-NOT-HUMAN-READABLE".to_string(),
tag_type: LDAPSchemaTagType::Boolean
},
];
}
ldap_schema_parser(&LDAP_SYNTAX_TAGS).try_map(|(oid, tags), span| {
Ok(LDAPSyntax {
oid,
desc: required_tag("DESC", &span, &tags)?
.as_string()
.unwrap()
.to_string(),
x_binary_transfer_required: *optional_tag("X-BINARY-TRANSFER-REQUIRED", &tags)
.unwrap_or(LDAPSchemaTagValue::Boolean(false))
.as_boolean()
.unwrap(),
x_not_human_readable: *optional_tag("X-NOT-HUMAN-READABLE", &tags)
.unwrap_or(LDAPSchemaTagValue::Boolean(false))
.as_boolean()
.unwrap(),
})
})
}
#[derive(Clone, Educe)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[educe(PartialEq, Eq, Hash)]
pub struct MatchingRule {
#[educe(Hash(method = "crate::basic::hash_oid"))]
pub oid: ObjectIdentifier,
pub name: Vec<KeyString>,
pub syntax: OIDWithLength,
}
impl std::fmt::Debug for MatchingRule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let string_oid: String = self.oid.clone().into();
f.debug_struct("MatchingRule")
.field("oid", &string_oid)
.field("name", &self.name)
.field("syntax", &self.syntax)
.finish()
}
}
#[cfg(feature = "chumsky")]
pub fn matching_rule_parser() -> impl Parser<char, MatchingRule, Error = Simple<char>> {
lazy_static! {
static ref MATCHING_RULE_TAGS: Vec<LDAPSchemaTagDescriptor> = vec![
LDAPSchemaTagDescriptor {
tag_name: "NAME".to_string(),
tag_type: LDAPSchemaTagType::QuotedKeyStringList
},
LDAPSchemaTagDescriptor {
tag_name: "SYNTAX".to_string(),
tag_type: LDAPSchemaTagType::OIDWithLength
},
];
}
ldap_schema_parser(&MATCHING_RULE_TAGS).try_map(|(oid, tags), span| {
Ok(MatchingRule {
oid,
name: required_tag("NAME", &span, &tags)?
.as_quoted_key_string_list()
.unwrap()
.to_vec(),
syntax: required_tag("SYNTAX", &span, &tags)?
.as_oid_with_length()
.unwrap()
.to_owned(),
})
})
}
#[derive(Clone, Educe)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[educe(PartialEq, Eq, Hash)]
pub struct MatchingRuleUse {
#[educe(Hash(method = "crate::basic::hash_oid"))]
pub oid: ObjectIdentifier,
pub name: Vec<KeyString>,
pub applies: Vec<KeyStringOrOID>,
}
impl std::fmt::Debug for MatchingRuleUse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let string_oid: String = self.oid.clone().into();
f.debug_struct("MatchingRuleUse")
.field("oid", &string_oid)
.field("name", &self.name)
.field("applies", &self.applies)
.finish()
}
}
#[cfg(feature = "chumsky")]
pub fn matching_rule_use_parser() -> impl Parser<char, MatchingRuleUse, Error = Simple<char>> {
lazy_static! {
static ref MATCHING_RULE_USE_TAGS: Vec<LDAPSchemaTagDescriptor> = vec![
LDAPSchemaTagDescriptor {
tag_name: "NAME".to_string(),
tag_type: LDAPSchemaTagType::QuotedKeyStringList
},
LDAPSchemaTagDescriptor {
tag_name: "APPLIES".to_string(),
tag_type: LDAPSchemaTagType::KeyStringOrOIDList
},
];
}
ldap_schema_parser(&MATCHING_RULE_USE_TAGS).try_map(|(oid, tags), span| {
Ok(MatchingRuleUse {
oid,
name: required_tag("NAME", &span, &tags)?
.as_quoted_key_string_list()
.unwrap()
.to_vec(),
applies: required_tag("APPLIES", &span, &tags)?
.as_key_string_or_oid_list()
.unwrap()
.to_vec(),
})
})
}
#[derive(Clone, Educe)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[educe(PartialEq, Eq, Hash)]
pub struct AttributeType {
#[educe(Hash(method = "crate::basic::hash_oid"))]
pub oid: ObjectIdentifier,
pub name: Vec<KeyString>,
pub sup: Option<KeyString>,
pub desc: Option<String>,
pub syntax: Option<OIDWithLength>,
pub single_value: bool,
pub equality: Option<KeyString>,
pub substr: Option<KeyString>,
pub ordering: Option<KeyString>,
pub no_user_modification: bool,
pub usage: Option<KeyString>,
pub collective: bool,
pub obsolete: bool,
pub x_ordered: Option<KeyString>,
}
impl std::fmt::Debug for AttributeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let string_oid: String = self.oid.clone().into();
f.debug_struct("AttributeType")
.field("oid", &string_oid)
.field("name", &self.name)
.field("sup", &self.sup)
.field("desc", &self.desc)
.field("syntax", &self.syntax)
.field("single_value", &self.single_value)
.field("equality", &self.equality)
.field("substr", &self.substr)
.field("ordering", &self.ordering)
.field("no_user_modification", &self.no_user_modification)
.field("usage", &self.usage)
.field("collective", &self.collective)
.field("obsolete", &self.obsolete)
.field("x_ordered", &self.x_ordered)
.finish()
}
}
#[cfg(feature = "chumsky")]
pub fn attribute_type_parser() -> impl Parser<char, AttributeType, Error = Simple<char>> {
lazy_static! {
static ref ATTRIBUTE_TYPE_TAGS: Vec<LDAPSchemaTagDescriptor> = vec![
LDAPSchemaTagDescriptor {
tag_name: "NAME".to_string(),
tag_type: LDAPSchemaTagType::QuotedKeyStringList
},
LDAPSchemaTagDescriptor {
tag_name: "SUP".to_string(),
tag_type: LDAPSchemaTagType::KeyString
},
LDAPSchemaTagDescriptor {
tag_name: "DESC".to_string(),
tag_type: LDAPSchemaTagType::String
},
LDAPSchemaTagDescriptor {
tag_name: "SYNTAX".to_string(),
tag_type: LDAPSchemaTagType::OIDWithLength
},
LDAPSchemaTagDescriptor {
tag_name: "EQUALITY".to_string(),
tag_type: LDAPSchemaTagType::KeyString
},
LDAPSchemaTagDescriptor {
tag_name: "SUBSTR".to_string(),
tag_type: LDAPSchemaTagType::KeyString
},
LDAPSchemaTagDescriptor {
tag_name: "ORDERING".to_string(),
tag_type: LDAPSchemaTagType::KeyString
},
LDAPSchemaTagDescriptor {
tag_name: "SINGLE-VALUE".to_string(),
tag_type: LDAPSchemaTagType::Standalone
},
LDAPSchemaTagDescriptor {
tag_name: "NO-USER-MODIFICATION".to_string(),
tag_type: LDAPSchemaTagType::Standalone
},
LDAPSchemaTagDescriptor {
tag_name: "USAGE".to_string(),
tag_type: LDAPSchemaTagType::KeyString
},
LDAPSchemaTagDescriptor {
tag_name: "COLLECTIVE".to_string(),
tag_type: LDAPSchemaTagType::Standalone
},
LDAPSchemaTagDescriptor {
tag_name: "OBSOLETE".to_string(),
tag_type: LDAPSchemaTagType::Standalone
},
LDAPSchemaTagDescriptor {
tag_name: "X-ORDERED".to_string(),
tag_type: LDAPSchemaTagType::QuotedKeyString
},
];
}
ldap_schema_parser(&ATTRIBUTE_TYPE_TAGS).try_map(|(oid, tags), span| {
Ok(AttributeType {
oid,
name: required_tag("NAME", &span, &tags)?
.as_quoted_key_string_list()
.unwrap()
.to_vec(),
sup: optional_tag("SUP", &tags).map(|s| s.as_key_string().unwrap().to_owned()),
desc: optional_tag("DESC", &tags).map(|v| v.as_string().unwrap().to_string()),
syntax: optional_tag("SYNTAX", &tags)
.map(|v| v.as_oid_with_length().unwrap().to_owned()),
single_value: optional_tag("SINGLE-VALUE", &tags).is_some(),
equality: optional_tag("EQUALITY", &tags)
.map(|s| s.as_key_string().unwrap().to_owned()),
substr: optional_tag("SUBSTR", &tags).map(|s| s.as_key_string().unwrap().to_owned()),
ordering: optional_tag("ORDERING", &tags)
.map(|s| s.as_key_string().unwrap().to_owned()),
no_user_modification: optional_tag("NO-USER-MODIFICATION", &tags).is_some(),
usage: optional_tag("USAGE", &tags).map(|s| s.as_key_string().unwrap().to_owned()),
collective: optional_tag("COLLECTIVE", &tags).is_some(),
obsolete: optional_tag("OBSOLETE", &tags).is_some(),
x_ordered: optional_tag("X-ORDERED", &tags)
.map(|s| s.as_quoted_key_string().unwrap().to_owned()),
})
})
}
#[derive(PartialEq, Eq, Clone, Debug, EnumAsInner, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ObjectClassType {
Abstract,
Structural,
Auxiliary,
}
#[derive(Clone, Educe)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[educe(PartialEq, Eq, Hash)]
pub struct ObjectClass {
#[educe(Hash(method = "crate::basic::hash_oid"))]
pub oid: ObjectIdentifier,
pub name: Vec<KeyString>,
pub sup: Vec<KeyStringOrOID>,
pub desc: Option<String>,
pub object_class_type: ObjectClassType,
pub must: Vec<KeyStringOrOID>,
pub may: Vec<KeyStringOrOID>,
pub obsolete: bool,
}
impl std::fmt::Debug for ObjectClass {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let string_oid: String = self.oid.clone().into();
f.debug_struct("ObjectClass")
.field("oid", &string_oid)
.field("name", &self.name)
.field("sup", &self.sup)
.field("desc", &self.desc)
.field("object_class_type", &self.object_class_type)
.field("must", &self.must)
.field("may", &self.may)
.field("obsolete", &self.obsolete)
.finish()
}
}
#[cfg(feature = "chumsky")]
pub fn object_class_parser() -> impl Parser<char, ObjectClass, Error = Simple<char>> {
lazy_static! {
static ref OBJECT_CLASS_TAGS: Vec<LDAPSchemaTagDescriptor> = vec![
LDAPSchemaTagDescriptor {
tag_name: "NAME".to_string(),
tag_type: LDAPSchemaTagType::QuotedKeyStringList
},
LDAPSchemaTagDescriptor {
tag_name: "SUP".to_string(),
tag_type: LDAPSchemaTagType::KeyStringOrOIDList
},
LDAPSchemaTagDescriptor {
tag_name: "DESC".to_string(),
tag_type: LDAPSchemaTagType::String
},
LDAPSchemaTagDescriptor {
tag_name: "ABSTRACT".to_string(),
tag_type: LDAPSchemaTagType::Standalone
},
LDAPSchemaTagDescriptor {
tag_name: "STRUCTURAL".to_string(),
tag_type: LDAPSchemaTagType::Standalone
},
LDAPSchemaTagDescriptor {
tag_name: "AUXILIARY".to_string(),
tag_type: LDAPSchemaTagType::Standalone
},
LDAPSchemaTagDescriptor {
tag_name: "MUST".to_string(),
tag_type: LDAPSchemaTagType::KeyStringOrOIDList
},
LDAPSchemaTagDescriptor {
tag_name: "MAY".to_string(),
tag_type: LDAPSchemaTagType::KeyStringOrOIDList
},
LDAPSchemaTagDescriptor {
tag_name: "OBSOLETE".to_string(),
tag_type: LDAPSchemaTagType::Standalone
},
];
}
ldap_schema_parser(&OBJECT_CLASS_TAGS).try_map(|(oid, tags), span| {
Ok(ObjectClass {
oid,
name: required_tag("NAME", &span, &tags)?
.as_quoted_key_string_list()
.unwrap()
.to_vec(),
sup: optional_tag("SUP", &tags)
.map(|s| s.as_key_string_or_oid_list().unwrap().to_owned())
.unwrap_or_default(),
desc: optional_tag("DESC", &tags).map(|v| v.as_string().unwrap().to_string()),
object_class_type: optional_tag("ABSTRACT", &tags)
.map(|_| ObjectClassType::Abstract)
.or_else(|| optional_tag("STRUCTURAL", &tags).map(|_| ObjectClassType::Structural))
.or_else(|| optional_tag("AUXILIARY", &tags).map(|_| ObjectClassType::Auxiliary))
.unwrap_or(ObjectClassType::Structural),
must: optional_tag("MUST", &tags)
.map(|v| v.as_key_string_or_oid_list().unwrap().to_vec())
.unwrap_or_default(),
may: optional_tag("MAY", &tags)
.map(|v| v.as_key_string_or_oid_list().unwrap().to_vec())
.unwrap_or_default(),
obsolete: optional_tag("OBSOLETE", &tags).is_some(),
})
})
}
#[derive(Debug, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LDAPSchema {
pub ldap_syntaxes: Vec<LDAPSyntax>,
pub matching_rules: Vec<MatchingRule>,
pub matching_rule_use: Vec<MatchingRuleUse>,
pub attribute_types: Vec<AttributeType>,
pub object_classes: Vec<ObjectClass>,
}
impl LDAPSchema {
pub fn allowed_attributes(
&self,
id: impl TryInto<KeyStringOrOID>,
) -> Option<HashSet<&AttributeType>> {
if let Some(object_class) = self.find_object_class(id) {
let mut result = HashSet::new();
for attribute_name in object_class.must.iter().chain(object_class.may.iter()) {
if let Some(attribute) = self.find_attribute_type(attribute_name) {
result.insert(attribute);
}
}
for sup in &object_class.sup {
if let Some(allowed_attributes) = self.allowed_attributes(sup) {
result.extend(allowed_attributes);
}
}
Some(result)
} else {
None
}
}
pub fn required_attributes(
&self,
id: impl TryInto<KeyStringOrOID>,
) -> Option<HashSet<&AttributeType>> {
if let Some(object_class) = self.find_object_class(id) {
let mut result = HashSet::new();
for attribute_name in object_class.must.iter() {
if let Some(attribute) = self.find_attribute_type(attribute_name) {
result.insert(attribute);
}
}
for sup in &object_class.sup {
if let Some(required_attributes) = self.required_attributes(sup) {
result.extend(required_attributes);
}
}
Some(result)
} else {
None
}
}
#[cfg(feature = "chumsky")]
pub fn find_object_class(&self, id: impl TryInto<KeyStringOrOID>) -> Option<&ObjectClass> {
let id: Result<KeyStringOrOID, _> = id.try_into();
match id {
Ok(id) => {
let match_fn: Box<dyn FnMut(&&ObjectClass) -> bool> = match id {
KeyStringOrOID::OID(oid) => Box::new(move |at: &&ObjectClass| at.oid == oid),
KeyStringOrOID::KeyString(s) => Box::new(move |at: &&ObjectClass| {
at.name
.iter()
.map(|n| n.to_lowercase())
.contains(&s.to_lowercase())
}),
};
self.object_classes.iter().find(match_fn)
}
Err(_) => None,
}
}
#[cfg(feature = "chumsky")]
pub fn find_object_class_property<'a, R>(
&'a self,
id: impl TryInto<KeyStringOrOID>,
f: fn(&'a ObjectClass) -> Option<&'a R>,
) -> Option<&'a R> {
let object_class = self.find_object_class(id);
if let Some(object_class) = object_class {
if let Some(r) = f(object_class) {
Some(r)
} else {
let ks_or_oids = &object_class.sup;
for ks_or_oid in ks_or_oids {
if let Some(r) = self.find_object_class_property(ks_or_oid, f) {
return Some(r);
}
}
None
}
} else {
None
}
}
#[cfg(feature = "chumsky")]
pub fn find_attribute_type(&self, id: impl TryInto<KeyStringOrOID>) -> Option<&AttributeType> {
let id: Result<KeyStringOrOID, _> = id.try_into();
match id {
Ok(id) => {
let match_fn: Box<dyn FnMut(&&AttributeType) -> bool> = match id {
KeyStringOrOID::OID(oid) => Box::new(move |at: &&AttributeType| at.oid == oid),
KeyStringOrOID::KeyString(s) => Box::new(move |at: &&AttributeType| {
at.name
.iter()
.map(|n| n.to_lowercase())
.contains(&s.to_lowercase())
}),
};
self.attribute_types.iter().find(match_fn)
}
Err(_) => None,
}
}
#[cfg(feature = "chumsky")]
pub fn find_attribute_type_property<'a, R>(
&'a self,
id: impl TryInto<KeyStringOrOID>,
f: fn(&'a AttributeType) -> Option<&'a R>,
) -> Option<&'a R> {
let attribute_type = self.find_attribute_type(id);
if let Some(attribute_type) = attribute_type {
if let Some(r) = f(attribute_type) {
Some(r)
} else if let Some(KeyString(sup)) = &attribute_type.sup {
self.find_attribute_type_property(sup, f)
} else {
None
}
} else {
None
}
}
#[cfg(feature = "chumsky")]
pub fn find_ldap_syntax(&self, id: impl TryInto<ObjectIdentifier>) -> Option<&LDAPSyntax> {
let id: Result<ObjectIdentifier, _> = id.try_into();
match id {
Ok(id) => self
.ldap_syntaxes
.iter()
.find(move |ls: &&LDAPSyntax| ls.oid == id),
Err(_) => None,
}
}
#[cfg(feature = "chumsky")]
pub fn find_matching_rule(&self, id: impl TryInto<ObjectIdentifier>) -> Option<&MatchingRule> {
let id: Result<ObjectIdentifier, _> = id.try_into();
match id {
Ok(id) => self
.matching_rules
.iter()
.find(move |ls: &&MatchingRule| ls.oid == id),
Err(_) => None,
}
}
#[cfg(feature = "chumsky")]
pub fn find_matching_rule_use(
&self,
id: impl TryInto<ObjectIdentifier>,
) -> Option<&MatchingRuleUse> {
let id: Result<ObjectIdentifier, _> = id.try_into();
match id {
Ok(id) => self
.matching_rule_use
.iter()
.find(move |ls: &&MatchingRuleUse| ls.oid == id),
Err(_) => None,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_ldap_syntax() {
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());
}
#[test]
fn test_parse_ldap_syntax_value1() {
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' )"),
Ok(LDAPSyntax { oid: "1.3.6.1.4.1.1466.115.121.1.8".to_string().try_into().unwrap(),
desc: "Certificate".to_string(),
x_binary_transfer_required: true,
x_not_human_readable: true,
}
));
}
#[test]
fn test_parse_ldap_syntax_value2() {
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' )"),
Ok(LDAPSyntax { oid: "1.3.6.1.4.1.1466.115.121.1.8".to_string().try_into().unwrap(),
desc: "Certificate".to_string(),
x_binary_transfer_required: true,
x_not_human_readable: true,
}
));
}
#[test]
fn test_parse_ldap_syntax_value3() {
assert_eq!(ldap_syntax_parser().parse("( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' X-BINARY-TRANSFER-REQUIRED 'TRUE' )"),
Ok(LDAPSyntax { oid: "1.3.6.1.4.1.1466.115.121.1.8".to_string().try_into().unwrap(),
desc: "Certificate".to_string(),
x_binary_transfer_required: true,
x_not_human_readable: false,
}
));
}
#[test]
fn test_parse_ldap_syntax_value4() {
assert_eq!(
ldap_syntax_parser().parse(
"( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' X-NOT-HUMAN-READABLE 'TRUE' )"
),
Ok(LDAPSyntax {
oid: "1.3.6.1.4.1.1466.115.121.1.8"
.to_string()
.try_into()
.unwrap(),
desc: "Certificate".to_string(),
x_binary_transfer_required: false,
x_not_human_readable: true,
})
);
}
#[test]
fn test_parse_ldap_syntax_value5() {
assert_eq!(
ldap_syntax_parser().parse("( 1.3.6.1.4.1.1466.115.121.1.8 DESC 'Certificate' )"),
Ok(LDAPSyntax {
oid: "1.3.6.1.4.1.1466.115.121.1.8"
.to_string()
.try_into()
.unwrap(),
desc: "Certificate".to_string(),
x_binary_transfer_required: false,
x_not_human_readable: false,
})
);
}
#[test]
fn test_parse_ldap_syntax_desc_required() {
assert!(ldap_syntax_parser()
.parse("( 1.3.6.1.4.1.1466.115.121.1.8 )")
.is_err());
}
#[test]
fn test_parse_matching_rule() {
assert!(matching_rule_parser()
.parse("( 1.3.6.1.1.16.3 NAME 'UUIDOrderingMatch' SYNTAX 1.3.6.1.1.16.1 )")
.is_ok());
}
#[test]
fn test_parse_matching_rule_value() {
assert_eq!(
matching_rule_parser()
.parse("( 1.3.6.1.1.16.3 NAME 'UUIDOrderingMatch' SYNTAX 1.3.6.1.1.16.1 )"),
Ok(MatchingRule {
oid: "1.3.6.1.1.16.3".to_string().try_into().unwrap(),
name: vec![KeyString("UUIDOrderingMatch".to_string())],
syntax: OIDWithLength {
oid: "1.3.6.1.1.16.1".to_string().try_into().unwrap(),
length: None
},
})
);
}
#[test]
fn test_parse_matching_rule_uses() {
assert!(matching_rule_use_parser().parse("( 2.5.13.11 NAME 'caseIgnoreListMatch' APPLIES ( postalAddress $ registeredAddress $ homePostalAddress ) )").is_ok());
}
#[test]
fn test_parse_matching_rule_uses_value() {
assert_eq!(matching_rule_use_parser().parse("( 2.5.13.11 NAME 'caseIgnoreListMatch' APPLIES ( postalAddress $ registeredAddress $ homePostalAddress ) )"),
Ok(MatchingRuleUse { oid: "2.5.13.11".to_string().try_into().unwrap(),
name: vec![KeyString("caseIgnoreListMatch".to_string())],
applies: vec![KeyStringOrOID::KeyString(KeyString("postalAddress".to_string())),
KeyStringOrOID::KeyString(KeyString("registeredAddress".to_string())),
KeyStringOrOID::KeyString(KeyString("homePostalAddress".to_string()))
],
})
);
}
#[test]
fn test_parse_matching_rule_uses_single_applies_value() {
assert_eq!(
matching_rule_use_parser()
.parse("( 2.5.13.11 NAME 'caseIgnoreListMatch' APPLIES postalAddress )"),
Ok(MatchingRuleUse {
oid: "2.5.13.11".to_string().try_into().unwrap(),
name: vec![KeyString("caseIgnoreListMatch".to_string())],
applies: vec![KeyStringOrOID::KeyString(KeyString(
"postalAddress".to_string()
))],
})
);
}
}