use std::ops::Deref;
use thiserror::Error;
use utoipa::ToSchema;
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Clone, Hash, ToSchema)]
#[schema(value_type = String)]
#[serde(try_from = "String")]
pub struct NameOrDescription(String);
const MAX_LEN: usize = 64;
#[derive(Debug, PartialEq, Eq, Error)]
pub enum NameOrDescriptionError {
#[error("Search query must not be longer than {MAX_LEN} characters")]
InvalidLength,
}
impl NameOrDescription {
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_inner(self) -> String {
self.0
}
}
impl TryFrom<String> for NameOrDescription {
type Error = NameOrDescriptionError;
fn try_from(value: String) -> Result<Self, Self::Error> {
if value.chars().count() > MAX_LEN {
Err(NameOrDescriptionError::InvalidLength)
} else {
Ok(Self(value))
}
}
}
impl TryFrom<&str> for NameOrDescription {
type Error = NameOrDescriptionError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::try_from(value.to_string())
}
}
impl From<NameOrDescription> for String {
fn from(value: NameOrDescription) -> Self {
value.0
}
}
impl Deref for NameOrDescription {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_arbitrary_text() {
assert_eq!(
NameOrDescription::try_from("vcard parser")
.unwrap()
.as_str(),
"vcard parser"
);
}
#[test]
fn accepts_query_at_the_limit() {
let query = "a".repeat(MAX_LEN);
assert_eq!(
NameOrDescription::try_from(query.clone()).unwrap().as_str(),
query
);
}
#[test]
fn rejects_query_over_the_limit() {
let query = "a".repeat(MAX_LEN + 1);
assert_eq!(
NameOrDescription::try_from(query),
Err(NameOrDescriptionError::InvalidLength)
);
}
#[test]
fn counts_characters_not_bytes() {
let query = "รค".repeat(MAX_LEN);
assert!(query.len() > MAX_LEN);
assert!(NameOrDescription::try_from(query).is_ok());
}
#[test]
fn deserialization_enforces_the_limit() {
let ok =
serde_json::from_value::<NameOrDescription>(serde_json::json!("a".repeat(MAX_LEN)));
assert!(ok.is_ok());
let too_long =
serde_json::from_value::<NameOrDescription>(serde_json::json!("a".repeat(MAX_LEN + 1)));
assert!(too_long.is_err());
}
}