use std::str::FromStr;
use snafu::Snafu;
use crate::utils::ExampleData;
pub const FILE_EXTENSION_MAX_LENGTH: usize = 10;
#[derive(Debug, Clone, Default, PartialEq, Eq, derive_more::Display)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde_with::DeserializeFromStr)
)]
pub struct FileExtension(String);
impl FileExtension {
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn to_string_with_leading_dot(&self) -> String {
if self.0.is_empty() {
String::new()
} else {
format!(".{}", self.0)
}
}
pub fn pdf() -> Self {
Self("pdf".to_string())
}
}
#[derive(Debug, Snafu)]
pub enum ParseFileExtensionError {
#[snafu(display("FileExtension must not be longer than {max_length} characters"))]
TooLong { max_length: usize },
#[snafu(display("FileExtension only allows alphanumeric characters"))]
InvalidCharacters,
}
impl FromStr for FileExtension {
type Err = ParseFileExtensionError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() > FILE_EXTENSION_MAX_LENGTH {
return Err(ParseFileExtensionError::TooLong {
max_length: FILE_EXTENSION_MAX_LENGTH,
});
}
if s.chars().any(|c| !c.is_ascii_alphanumeric()) {
return Err(ParseFileExtensionError::InvalidCharacters);
}
Ok(FileExtension(s.into()))
}
}
#[cfg(feature = "utoipa")]
mod impl_utoipa {
use serde_json::json;
use utoipa::{
PartialSchema, ToSchema,
openapi::{ObjectBuilder, RefOr, Schema, Type},
};
use super::{FILE_EXTENSION_MAX_LENGTH, FileExtension};
use crate::utils::ExampleData;
impl PartialSchema for FileExtension {
fn schema() -> RefOr<Schema> {
ObjectBuilder::new()
.schema_type(Type::String)
.max_length(Some(FILE_EXTENSION_MAX_LENGTH))
.pattern(Some("^[0-9a-zA-Z]*$".to_string()))
.description(Some("An extension for a file path"))
.examples([json!(FileExtension::example_data())])
.into()
}
}
impl ToSchema for FileExtension {
fn schemas(schemas: &mut Vec<(String, RefOr<Schema>)>) {
schemas.push((Self::name().into(), Self::schema()));
}
}
}
impl ExampleData for FileExtension {
fn example_data() -> Self {
Self::pdf()
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::FileExtension;
#[test]
fn file_extension_to_string_with_leading_dot() {
assert_eq!(
"".to_string(),
FileExtension("".to_string()).to_string_with_leading_dot()
);
assert_eq!(
".7z".to_string(),
FileExtension("7z".to_string()).to_string_with_leading_dot()
);
assert_eq!(
".txt".to_string(),
FileExtension("txt".to_string()).to_string_with_leading_dot()
);
}
}
#[cfg(all(test, feature = "serde"))]
mod serde_tests {
use pretty_assertions::assert_eq;
use serde_json::json;
use super::FileExtension;
#[test]
fn file_extension_deserialization() {
assert_eq!(
FileExtension("".to_string()),
serde_json::from_value::<FileExtension>(json!("")).unwrap()
);
assert_eq!(
FileExtension("hello".to_string()),
serde_json::from_value::<FileExtension>(json!("hello")).unwrap()
);
assert_eq!(
FileExtension("HeLlOwOrLd".to_string()),
serde_json::from_value::<FileExtension>(json!("HeLlOwOrLd")).unwrap()
);
assert_eq!(
FileExtension("1337".to_string()),
serde_json::from_value::<FileExtension>(json!("1337")).unwrap()
);
assert_eq!(
FileExtension("7z".to_string()),
serde_json::from_value::<FileExtension>(json!("7z")).unwrap()
);
assert!(serde_json::from_value::<FileExtension>(json!("HeLlOnIcEwOrLd")).is_err());
assert!(serde_json::from_value::<FileExtension>(json!("hi world")).is_err());
assert!(serde_json::from_value::<FileExtension>(json!("Hello!")).is_err());
assert!(serde_json::from_value::<FileExtension>(json!("nice.try")).is_err());
assert!(serde_json::from_value::<FileExtension>(json!("世界您好")).is_err());
}
}