use crate::error::{ValidationError, ValidationResult};
use crate::resource::value_objects::{MultiValuedAttribute, ResourceId};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GroupMember {
value: ResourceId,
#[serde(skip_serializing_if = "Option::is_none")]
display: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "type")]
member_type: Option<String>,
}
impl GroupMember {
pub fn new(
value: ResourceId,
display: Option<String>,
member_type: Option<String>,
) -> ValidationResult<Self> {
if let Some(ref display_name) = display {
Self::validate_display_name(display_name)?;
}
if let Some(ref mtype) = member_type {
Self::validate_member_type(mtype)?;
}
Ok(Self {
value,
display,
member_type,
})
}
pub fn new_user(value: ResourceId, display: Option<String>) -> ValidationResult<Self> {
Self::new(value, display, Some("User".to_string()))
}
pub fn new_group(value: ResourceId, display: Option<String>) -> ValidationResult<Self> {
Self::new(value, display, Some("Group".to_string()))
}
pub fn value(&self) -> &ResourceId {
&self.value
}
pub fn display_name(&self) -> Option<&str> {
self.display.as_deref()
}
pub fn member_type(&self) -> Option<&str> {
self.member_type.as_deref()
}
pub fn is_user(&self) -> bool {
self.member_type.as_deref() == Some("User")
}
pub fn is_group(&self) -> bool {
self.member_type.as_deref() == Some("Group")
}
pub fn effective_display_name(&self) -> &str {
self.display.as_deref().unwrap_or(self.value.as_str())
}
fn validate_display_name(display_name: &str) -> ValidationResult<()> {
if display_name.is_empty() {
return Err(ValidationError::custom("Display name cannot be empty"));
}
if display_name.len() > 256 {
return Err(ValidationError::custom(
"Display name cannot exceed 256 characters",
));
}
if display_name.chars().any(|c| c.is_control() && c != '\t') {
return Err(ValidationError::custom(
"Display name cannot contain control characters",
));
}
Ok(())
}
fn validate_member_type(member_type: &str) -> ValidationResult<()> {
if member_type.is_empty() {
return Err(ValidationError::custom("Member type cannot be empty"));
}
match member_type {
"User" | "Group" => Ok(()),
_ => Err(ValidationError::custom(format!(
"Invalid member type '{}'. Must be 'User' or 'Group'",
member_type
))),
}
}
}
impl fmt::Display for GroupMember {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (&self.display, &self.member_type) {
(Some(display), Some(mtype)) => {
write!(f, "{} ({}) [{}]", display, mtype, self.value.as_str())
}
(Some(display), None) => write!(f, "{} [{}]", display, self.value.as_str()),
(None, Some(mtype)) => write!(f, "({}) [{}]", mtype, self.value.as_str()),
(None, None) => write!(f, "[{}]", self.value.as_str()),
}
}
}
pub type GroupMembers = MultiValuedAttribute<GroupMember>;
pub type MultiValuedEmails = MultiValuedAttribute<crate::resource::value_objects::EmailAddress>;
pub type MultiValuedAddresses = MultiValuedAttribute<crate::resource::value_objects::Address>;
pub type MultiValuedPhoneNumbers =
MultiValuedAttribute<crate::resource::value_objects::PhoneNumber>;
#[cfg(test)]
mod tests {
use super::*;
fn create_test_resource_id(id: &str) -> ResourceId {
ResourceId::new(id.to_string()).unwrap()
}
#[test]
fn test_group_member_new_valid() {
let member_id = create_test_resource_id("user-123");
let member = GroupMember::new(
member_id.clone(),
Some("John Doe".to_string()),
Some("User".to_string()),
)
.unwrap();
assert_eq!(member.value(), &member_id);
assert_eq!(member.display_name(), Some("John Doe"));
assert_eq!(member.member_type(), Some("User"));
assert!(member.is_user());
assert!(!member.is_group());
}
#[test]
fn test_group_member_new_user() {
let member_id = create_test_resource_id("user-123");
let member =
GroupMember::new_user(member_id.clone(), Some("John Doe".to_string())).unwrap();
assert_eq!(member.value(), &member_id);
assert_eq!(member.display_name(), Some("John Doe"));
assert_eq!(member.member_type(), Some("User"));
assert!(member.is_user());
}
#[test]
fn test_group_member_new_group() {
let group_id = create_test_resource_id("group-456");
let member =
GroupMember::new_group(group_id.clone(), Some("Admin Group".to_string())).unwrap();
assert_eq!(member.value(), &group_id);
assert_eq!(member.display_name(), Some("Admin Group"));
assert_eq!(member.member_type(), Some("Group"));
assert!(member.is_group());
}
#[test]
fn test_group_member_minimal() {
let member_id = create_test_resource_id("user-123");
let member = GroupMember::new(member_id.clone(), None, None).unwrap();
assert_eq!(member.value(), &member_id);
assert_eq!(member.display_name(), None);
assert_eq!(member.member_type(), None);
assert!(!member.is_user());
assert!(!member.is_group());
}
#[test]
fn test_group_member_invalid_display_name() {
let member_id = create_test_resource_id("user-123");
let result = GroupMember::new(member_id.clone(), Some("".to_string()), None);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("cannot be empty"));
let long_name = "a".repeat(257);
let result = GroupMember::new(member_id.clone(), Some(long_name), None);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("cannot exceed 256")
);
let result = GroupMember::new(member_id, Some("John\x00Doe".to_string()), None);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("control characters")
);
}
#[test]
fn test_group_member_invalid_member_type() {
let member_id = create_test_resource_id("user-123");
let result = GroupMember::new(member_id.clone(), None, Some("".to_string()));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("cannot be empty"));
let result = GroupMember::new(member_id, None, Some("Invalid".to_string()));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Invalid member type")
);
}
#[test]
fn test_group_member_effective_display_name() {
let member_id = create_test_resource_id("user-123");
let member_with_name =
GroupMember::new_user(member_id.clone(), Some("John Doe".to_string())).unwrap();
assert_eq!(member_with_name.effective_display_name(), "John Doe");
let member_without_name = GroupMember::new_user(member_id.clone(), None).unwrap();
assert_eq!(member_without_name.effective_display_name(), "user-123");
}
#[test]
fn test_group_member_display() {
let member_id = create_test_resource_id("user-123");
let full_member = GroupMember::new(
member_id.clone(),
Some("John Doe".to_string()),
Some("User".to_string()),
)
.unwrap();
let display_str = format!("{}", full_member);
assert!(display_str.contains("John Doe"));
assert!(display_str.contains("User"));
assert!(display_str.contains("user-123"));
let display_only =
GroupMember::new(member_id.clone(), Some("John Doe".to_string()), None).unwrap();
let display_str = format!("{}", display_only);
assert!(display_str.contains("John Doe"));
assert!(display_str.contains("user-123"));
let type_only =
GroupMember::new(member_id.clone(), None, Some("User".to_string())).unwrap();
let display_str = format!("{}", type_only);
assert!(display_str.contains("User"));
assert!(display_str.contains("user-123"));
let minimal = GroupMember::new(member_id.clone(), None, None).unwrap();
let display_str = format!("{}", minimal);
assert_eq!(display_str, "[user-123]");
}
#[test]
fn test_group_members_collection() {
let member1 = GroupMember::new_user(
create_test_resource_id("user-1"),
Some("John Doe".to_string()),
)
.unwrap();
let member2 = GroupMember::new_user(
create_test_resource_id("user-2"),
Some("Jane Smith".to_string()),
)
.unwrap();
let members = vec![member1.clone(), member2.clone()];
let group_members = GroupMembers::new(members).unwrap();
assert_eq!(group_members.len(), 2);
assert_eq!(group_members.get(0), Some(&member1));
assert_eq!(group_members.get(1), Some(&member2));
}
#[test]
fn test_group_members_with_primary() {
let member1 = GroupMember::new_user(
create_test_resource_id("user-1"),
Some("John Doe".to_string()),
)
.unwrap();
let member2 = GroupMember::new_user(
create_test_resource_id("user-2"),
Some("Jane Smith".to_string()),
)
.unwrap();
let members = vec![member1.clone(), member2.clone()];
let group_members = GroupMembers::new(members).unwrap().with_primary(1).unwrap();
assert_eq!(group_members.primary(), Some(&member2));
assert_eq!(group_members.primary_index(), Some(1));
}
#[test]
fn test_serialization() {
let member_id = create_test_resource_id("user-123");
let member = GroupMember::new(
member_id,
Some("John Doe".to_string()),
Some("User".to_string()),
)
.unwrap();
let json = serde_json::to_string(&member).unwrap();
let deserialized: GroupMember = serde_json::from_str(&json).unwrap();
assert_eq!(member, deserialized);
}
#[test]
fn test_serialization_optional_fields() {
let member_id = create_test_resource_id("user-123");
let member = GroupMember::new(member_id, None, None).unwrap();
let json = serde_json::to_string(&member).unwrap();
assert!(!json.contains("display"));
assert!(!json.contains("type"));
let deserialized: GroupMember = serde_json::from_str(&json).unwrap();
assert_eq!(member, deserialized);
}
}