Skip to content
This repository was archived by the owner on Aug 15, 2025. It is now read-only.

Commit d9b78ab

Browse files
committed
feat: impl TryFrom<&[AttributeTypeAndValue]> for DomainName
1 parent e4ab51f commit d9b78ab

File tree

1 file changed

+103
-1
lines changed

1 file changed

+103
-1
lines changed

src/types/federation_id.rs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
use der::Encode;
66
use regex::Regex;
77
use x509_cert::attr::AttributeTypeAndValue;
8+
use x509_cert::name::RdnSequence;
89

910
use crate::errors::{ConstraintError, ERR_MSG_FEDERATION_ID_REGEX, InvalidInput};
10-
use crate::{Constrained, OID_RDN_UID};
11+
use crate::{Constrained, OID_RDN_DOMAIN_COMPONENT, OID_RDN_UID};
1112

1213
/// The regular expression for a valid `FederationId`.
1314
pub static REGEX_FEDERATION_ID: &str = r"\b([a-z0-9._%+-]+)@([a-z0-9-]+(\.[a-z0-9-]+)*)$";
@@ -51,6 +52,41 @@ impl std::fmt::Display for DomainName {
5152
}
5253
}
5354

55+
impl TryFrom<&[AttributeTypeAndValue]> for DomainName {
56+
type Error = ConstraintError;
57+
58+
fn try_from(values: &[AttributeTypeAndValue]) -> Result<Self, Self::Error> {
59+
if let Some(non_rdn) = values
60+
.iter()
61+
.find(|element| element.oid != OID_RDN_DOMAIN_COMPONENT)
62+
{
63+
return Err(ConstraintError::Malformed(Some(format!(
64+
"Found a value with OID {} when expecting only DomainComponent values of OID {OID_RDN_DOMAIN_COMPONENT}",
65+
non_rdn.oid
66+
))));
67+
}
68+
let mut domain_components = Vec::with_capacity(values.len());
69+
for value in values.iter() {
70+
let attribute_value = value.value.value();
71+
let string = String::from_utf8_lossy(attribute_value);
72+
domain_components.push(string);
73+
}
74+
DomainName::new(domain_components.join(".").as_str())
75+
}
76+
}
77+
78+
impl From<DomainName> for AttributeTypeAndValue {
79+
fn from(value: DomainName) -> Self {
80+
todo!()
81+
}
82+
}
83+
84+
impl From<DomainName> for RdnSequence {
85+
fn from(value: DomainName) -> Self {
86+
todo!()
87+
}
88+
}
89+
5490
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
5591
/// A `FederationId` is a globally unique identifier for an actor in the context of polyproto.
5692
pub struct FederationId {
@@ -248,6 +284,7 @@ impl TryFrom<AttributeTypeAndValue> for FederationId {
248284
}
249285

250286
#[cfg(test)]
287+
#[allow(clippy::unwrap_used)]
251288
mod test {
252289
use der::Any;
253290
use x509_cert::ext::pkix::name::DirectoryString;
@@ -271,4 +308,69 @@ mod test {
271308
};
272309
assert!(FederationId::try_from(attribute_and_value).is_err())
273310
}
311+
312+
#[test]
313+
#[allow(clippy::unwrap_used)]
314+
fn domain_name_from_attribute_type_and_value_success() {
315+
// Test successful parsing with valid domain components
316+
let domain_components = vec![
317+
AttributeTypeAndValue {
318+
oid: OID_RDN_DOMAIN_COMPONENT,
319+
value: Any::encode_from(&DirectoryString::Utf8String("example".to_string()))
320+
.unwrap(),
321+
},
322+
AttributeTypeAndValue {
323+
oid: OID_RDN_DOMAIN_COMPONENT,
324+
value: Any::encode_from(&DirectoryString::Utf8String("com".to_string())).unwrap(),
325+
},
326+
];
327+
328+
let result = DomainName::try_from(domain_components.as_slice());
329+
assert!(result.is_ok());
330+
let domain_name = result.unwrap();
331+
assert_eq!(domain_name.to_string(), "example.com");
332+
}
333+
334+
#[test]
335+
#[allow(clippy::unwrap_used)]
336+
fn domain_name_from_attribute_type_and_value_invalid_oid() {
337+
// Test error case with invalid OID
338+
let invalid_components = vec![
339+
AttributeTypeAndValue {
340+
oid: OID_RDN_DOMAIN_COMPONENT,
341+
value: Any::encode_from(&DirectoryString::Utf8String("example".to_string()))
342+
.unwrap(),
343+
},
344+
AttributeTypeAndValue {
345+
oid: OID_RDN_UID, // Wrong OID - should be OID_RDN_DOMAIN_COMPONENT
346+
value: Any::encode_from(&DirectoryString::Utf8String("com".to_string())).unwrap(),
347+
},
348+
];
349+
350+
let result = DomainName::try_from(invalid_components.as_slice());
351+
assert!(result.is_err());
352+
match result.unwrap_err() {
353+
ConstraintError::Malformed(Some(msg)) => {
354+
assert!(msg.contains("Found a value with OID"));
355+
assert!(msg.contains("when expecting only DomainComponent values"));
356+
}
357+
_ => panic!("Expected ConstraintError::Malformed with message"),
358+
}
359+
}
360+
361+
#[test]
362+
fn domain_name_from_empty_attribute_array() {
363+
// Test edge case with empty input
364+
let empty_components: Vec<AttributeTypeAndValue> = vec![];
365+
let result = DomainName::try_from(empty_components.as_slice());
366+
367+
// Empty input should attempt to create an empty domain name, which should fail validation
368+
assert!(result.is_err());
369+
match result.unwrap_err() {
370+
ConstraintError::Malformed(Some(msg)) => {
371+
assert!(msg.contains("does not match regex"));
372+
}
373+
_ => panic!("Expected ConstraintError::Malformed for invalid domain name"),
374+
}
375+
}
274376
}

0 commit comments

Comments
 (0)