use std::str::FromStr;
use itertools::Itertools as _;
use snafu::{Snafu, ensure};
use crate::utils::ExampleData;
pub const DISPLAY_NAME_MAX_LENGTH: usize = 255;
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Display)]
#[cfg_attr(
feature = "diesel",
derive(
opentalk_diesel_newtype::DieselNewtype,
diesel::expression::AsExpression,
diesel::deserialize::FromSqlRow
)
)]
#[cfg_attr(
feature = "diesel",
diesel(sql_type = diesel::sql_types::Text)
)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde_with::DeserializeFromStr)
)]
pub struct DisplayName(String);
impl DisplayName {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn new() -> Self {
Self::default()
}
pub fn from_str_lossy(s: &str) -> Self {
Self(
s.split_whitespace()
.join(" ")
.chars()
.take(DISPLAY_NAME_MAX_LENGTH)
.collect::<String>()
.trim()
.to_string(),
)
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn participant() -> Self {
Self("Participant".to_string())
}
pub fn len(&self) -> usize {
self.0.chars().count()
}
}
#[cfg(feature = "utoipa")]
mod impl_to_schema {
use serde_json::json;
use utoipa::{
PartialSchema, ToSchema,
openapi::{ObjectBuilder, RefOr, Schema, Type},
};
use super::{DISPLAY_NAME_MAX_LENGTH, DisplayName};
use crate::utils::ExampleData as _;
impl PartialSchema for DisplayName {
fn schema() -> RefOr<Schema> {
ObjectBuilder::new()
.schema_type(Type::String)
.description(Some("The display name of a user or participant"))
.max_length(Some(DISPLAY_NAME_MAX_LENGTH))
.examples([json!(DisplayName::example_data())])
.into()
}
}
impl ToSchema for DisplayName {
fn schemas(schemas: &mut Vec<(String, RefOr<Schema>)>) {
schemas.push((Self::name().into(), Self::schema()));
}
}
}
impl ExampleData for DisplayName {
fn example_data() -> Self {
Self("Alice Adams".to_string())
}
}
#[derive(Debug, Snafu)]
pub enum ParseDisplayNameError {
#[snafu(display("Display name must not be longer than {max_length} characters"))]
TooLong {
max_length: usize,
},
}
impl FromStr for DisplayName {
type Err = ParseDisplayNameError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
ensure!(
s.len() <= DISPLAY_NAME_MAX_LENGTH,
TooLongSnafu {
max_length: DISPLAY_NAME_MAX_LENGTH
}
);
Ok(Self(s.to_string()))
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::{DisplayName, ParseDisplayNameError};
#[test]
fn parse() {
assert_eq!(
"hello".parse::<DisplayName>().unwrap(),
DisplayName("hello".to_string())
);
assert_eq!(
"".parse::<DisplayName>().unwrap(),
DisplayName("".to_string())
);
assert_eq!(
"_".parse::<DisplayName>().unwrap(),
DisplayName("_".to_string())
);
assert_eq!(
"hello world".parse::<DisplayName>().unwrap(),
DisplayName("hello world".to_string())
);
assert_eq!(
"🚀".parse::<DisplayName>().unwrap(),
DisplayName("🚀".to_string())
);
let longest: String = "x".repeat(255);
assert_eq!(
longest.parse::<DisplayName>().unwrap(),
DisplayName(longest)
);
}
#[test]
fn parse_invalid() {
let too_long: String = "x".repeat(256);
assert!(matches!(
too_long.parse::<DisplayName>(),
Err(ParseDisplayNameError::TooLong { max_length: 255 })
));
}
#[test]
fn from_str_lossy_leading_spaces() {
assert_eq!(
DisplayName::from_str_lossy(" First Last"),
DisplayName("First Last".to_string()),
);
}
#[test]
fn from_str_lossy_trailing_spaces() {
assert_eq!(
DisplayName::from_str_lossy("First Last "),
DisplayName("First Last".to_string()),
);
}
#[test]
fn from_str_lossy_spaces_between() {
assert_eq!(
DisplayName::from_str_lossy("First Last"),
DisplayName("First Last".to_string()),
);
}
}