use crate::{domain::DataDomainError, validate_id};
use serde::{Deserialize, Serialize};
use std::cmp::PartialEq;
use utoipa::{IntoParams, ToSchema};
use uuid::Uuid;
use validator::Validate;
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, IntoParams, Validate)]
pub struct Author {
#[validate(custom(function = "validate_id"))]
#[schema(value_type = String, example = "0191e13b-5ab7-78f1-bc06-be503a6c111b")]
id: Option<Uuid>,
#[validate(length(min = 2), length(max = 40))]
name: Option<String>,
#[validate(length(min = 2), length(max = 40))]
surname: Option<String>,
#[validate(email)]
email: Option<String>,
pub shareable: Option<bool>,
#[validate(length(max = 255))]
description: Option<String>,
#[validate(url)]
website: Option<String>,
social_profiles: Option<Vec<SocialProfile>>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate, PartialEq)]
pub struct SocialProfile {
#[validate(length(max = 40))]
pub provider_name: String,
#[validate(length(max = 80))]
pub website: String,
}
#[derive(Default)]
pub struct AuthorBuilder {
id: Option<String>,
name: Option<String>,
surname: Option<String>,
email: Option<String>,
shareable: bool,
description: Option<String>,
website: Option<String>,
social_profiles: Option<Vec<SocialProfile>>,
}
impl std::default::Default for Author {
fn default() -> Self {
Author {
id: Some(Uuid::now_v7()),
name: None,
surname: None,
email: None,
shareable: Some(false),
description: None,
website: None,
social_profiles: None,
}
}
}
impl Author {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: Option<String>,
name: Option<String>,
surname: Option<String>,
email: Option<String>,
shareable: Option<bool>,
description: Option<String>,
website: Option<String>,
social_profiles: Option<&[SocialProfile]>,
) -> Result<Self, DataDomainError> {
let id = if id.is_some() {
match Uuid::parse_str(&id.unwrap()) {
Ok(id) => Some(id),
Err(_) => return Err(DataDomainError::InvalidId),
}
} else {
None
};
let author = Author {
id,
name,
surname,
email,
shareable,
description,
website,
social_profiles: social_profiles.map(Vec::from),
};
match author.validate() {
Ok(_) => std::result::Result::Ok(author),
Err(e) => Err(DataDomainError::InvalidParams { source: e }),
}
}
pub fn id(&self) -> Option<String> {
self.id.map(|id| id.to_string())
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn surname(&self) -> Option<&str> {
self.surname.as_deref()
}
pub fn email(&self) -> Option<&str> {
self.email.as_deref()
}
pub fn shareable(&self) -> bool {
self.shareable.unwrap_or_default()
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn website(&self) -> Option<&str> {
self.website.as_deref()
}
pub fn social_profiles(&self) -> Option<&[SocialProfile]> {
self.social_profiles.as_deref()
}
pub fn mute_private_data(&mut self) {
if !self.shareable() {
self.email = None;
self.description = None;
}
}
pub fn enable_sharing(&mut self) {
self.shareable = Some(true);
}
pub fn disable_sharing(&mut self) {
self.shareable = Some(false);
}
pub fn update_from(&mut self, update: &Author) {
if update.id().is_some() {
self.id = Some(Uuid::parse_str(&update.id().unwrap()).unwrap());
}
if update.name().is_some() {
self.name = Some(update.name().unwrap().into());
}
if update.surname().is_some() {
self.surname = Some(update.surname().unwrap().into());
}
if update.email().is_some() {
self.email = Some(update.email().unwrap().into());
}
if update.description().is_some() {
self.description = Some(update.description().unwrap().into());
}
if update.website().is_some() {
self.website = Some(update.website().unwrap().into());
}
if update.social_profiles().is_some() {
self.social_profiles = Some(Vec::from(update.social_profiles().unwrap()));
}
}
}
impl PartialEq for Author {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.surname == other.surname
&& self.email == other.email
&& self.shareable == other.shareable
&& self.description == other.description
&& self.social_profiles == other.social_profiles
}
}
impl AuthorBuilder {
pub fn set_id(mut self, id: &str) -> Self {
self.id = Some(id.into());
self
}
pub fn set_name(mut self, name: &str) -> Self {
self.name = Some(name.into());
self
}
pub fn set_surname(mut self, surname: &str) -> Self {
self.surname = Some(surname.into());
self
}
pub fn set_email(mut self, email: &str) -> Self {
self.email = Some(email.into());
self
}
pub fn set_shareable(mut self, shareable: bool) -> Self {
self.shareable = shareable;
self
}
pub fn set_description(mut self, description: &str) -> Self {
self.description = Some(description.into());
self
}
pub fn set_website(mut self, website: &str) -> Self {
self.website = Some(website.into());
self
}
pub fn set_social_profiles(mut self, profiles: &[SocialProfile]) -> Self {
self.social_profiles = Some(Vec::from(profiles));
self
}
pub fn build(self) -> Result<Author, DataDomainError> {
Author::new(
self.id,
self.name,
self.surname,
self.email,
Some(self.shareable),
self.description,
self.website,
self.social_profiles.as_deref(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use uuid::Uuid;
#[test]
fn build_author_using_builder() {
let author = AuthorBuilder::default().build().unwrap();
assert_eq!(author.id, None);
assert_eq!(author.name, None);
assert_eq!(author.surname, None);
assert_eq!(author.email, None);
assert_eq!(author.shareable, Some(false));
assert_eq!(author.description, None);
assert_eq!(author.website, None);
assert!(author.social_profiles.is_none());
let id = Uuid::now_v7().to_string();
let social_profiles = [
SocialProfile {
provider_name: "Facebook".into(),
website: "a web site".into(),
},
SocialProfile {
provider_name: "Instragram".into(),
website: "a web site".into(),
},
];
let author = AuthorBuilder::default()
.set_id(&id)
.set_name("Jane")
.set_surname("Doe")
.set_email("jane_doe@mail.com")
.set_description("An unknown person.")
.set_website("http://janedoe.com")
.set_shareable(true)
.set_social_profiles(&social_profiles)
.build()
.expect("Failed to build author");
assert_eq!(author.id().unwrap(), id);
assert_eq!(author.name().unwrap(), "Jane");
assert_eq!(author.surname().unwrap(), "Doe");
assert_eq!(author.email().unwrap(), "jane_doe@mail.com");
assert_eq!(author.shareable(), true);
assert_eq!(author.description().unwrap(), "An unknown person.");
assert_eq!(author.website().unwrap(), "http://janedoe.com");
assert_eq!(author.social_profiles().unwrap(), social_profiles);
}
#[test]
fn build_author() {
let mut author = Author::default();
assert!(Uuid::parse_str(&author.id().unwrap()).is_ok());
assert_eq!(author.name(), None);
assert!(!author.shareable());
author.enable_sharing();
assert!(author.shareable());
author.disable_sharing();
assert!(!author.shareable());
let id = Uuid::now_v7().to_string();
let social_profiles = [
SocialProfile {
provider_name: "Facebook".into(),
website: "a web site".into(),
},
SocialProfile {
provider_name: "Instragram".into(),
website: "a web site".into(),
},
];
let author = Author::new(
Some(id.clone()),
Some("Jane".to_string()),
Some("Doe".to_string()),
Some("jane_doe@mail.com".to_string()),
Some(true),
Some("An unknown person.".to_string()),
Some("http://janedoe.com".to_string()),
Some(&social_profiles),
)
.expect("Failed to create new instance of Author using new.");
assert_eq!(author.id().unwrap(), id);
assert_eq!(author.name().unwrap(), "Jane");
assert_eq!(author.surname().unwrap(), "Doe");
assert_eq!(author.email().unwrap(), "jane_doe@mail.com");
assert_eq!(author.shareable(), true);
assert_eq!(author.description().unwrap(), "An unknown person.");
assert_eq!(author.website().unwrap(), "http://janedoe.com");
assert_eq!(author.social_profiles().unwrap(), social_profiles);
}
#[test]
fn build_author_using_wrong_id() {
let author = AuthorBuilder::default().set_id("Wrong_ID").build();
assert!(author.is_err());
let author = AuthorBuilder::default().set_id("191919-010010-022").build();
assert!(author.is_err());
}
#[test]
fn build_author_using_wrong_text_length() {
let author = AuthorBuilder::default().set_name("J").build();
assert!(author.is_err());
let author = AuthorBuilder::default().set_surname("D").build();
assert!(author.is_err());
let author = AuthorBuilder::default().set_website("janedoe.com").build();
assert!(author.is_err());
let author = AuthorBuilder::default()
.set_email("janedoe<at>mail.com")
.build();
assert!(author.is_err());
let author = AuthorBuilder::default()
.set_description(&"dummy string".repeat(300))
.build();
assert!(author.is_err());
}
#[test]
fn mute_fields() {
let id = Uuid::now_v7().to_string();
let social_profiles = [
SocialProfile {
provider_name: "Facebook".into(),
website: "a web site".into(),
},
SocialProfile {
provider_name: "Instragram".into(),
website: "a web site".into(),
},
];
let mut author = Author::new(
Some(id.clone()),
Some("Jane".to_string()),
Some("Doe".to_string()),
Some("jane_doe@mail.com".to_string()),
Some(false),
Some("An unknown person.".to_string()),
Some("http://janedoe.com".to_string()),
Some(&social_profiles),
)
.expect("Failed to create new instance of Author using new.");
author.mute_private_data();
assert_eq!(author.id().unwrap(), id);
assert_eq!(author.name().unwrap(), "Jane");
assert_eq!(author.surname().unwrap(), "Doe");
assert_eq!(author.email(), None);
assert_eq!(author.shareable(), false);
assert_eq!(author.description(), None);
assert_eq!(author.website().unwrap(), "http://janedoe.com");
assert_eq!(author.social_profiles().unwrap(), social_profiles);
}
#[test]
fn modify_from() {
let id = Uuid::now_v7().to_string();
let name = "Jane";
let surname = "Doe";
let email = "jane@mail.com";
let website = "https://jane.com";
let description = "A dummy description";
let profiles = &[SocialProfile {
provider_name: "None".into(),
website: "https://none.com/jane".into(),
}];
let mut author = AuthorBuilder::default()
.set_id(&id)
.set_name(name)
.set_surname(surname)
.set_email(email)
.set_description(description)
.set_shareable(true)
.set_website(website)
.set_social_profiles(profiles)
.build()
.expect("Failed to build an author");
let author_dummy = AuthorBuilder::default()
.set_name("Stripped")
.set_surname("Zebra")
.build()
.expect("Failed to build an author");
author.update_from(&author_dummy);
assert_eq!(author.name, author_dummy.name);
assert_eq!(author.surname, author_dummy.surname);
assert_ne!(author.id, author_dummy.id);
assert_ne!(author.email, author_dummy.email);
assert_ne!(author.website, author_dummy.website);
assert_ne!(author.description, author_dummy.description);
assert_ne!(author.social_profiles, author_dummy.social_profiles);
let name = "Juana";
let surname = "Cierva";
let email = "juana@mail.com";
let website = "https://juana.com";
let description = "Una descripción vana";
let profiles = &[SocialProfile {
provider_name: "None".into(),
website: "https://none.com/juana".into(),
}];
let author_spa = AuthorBuilder::default()
.set_id(&id)
.set_name(name)
.set_surname(surname)
.set_email(email)
.set_description(description)
.set_shareable(true)
.set_website(website)
.set_social_profiles(profiles)
.build()
.expect("Failed to build an author");
author.update_from(&author_spa);
assert_eq!(author, author_spa);
}
}