winget_types/installer/
file_extension.rs1use core::{fmt, str::FromStr};
2
3use compact_str::CompactString;
4use thiserror::Error;
5
6use crate::DISALLOWED_CHARACTERS;
7
8#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[cfg_attr(feature = "serde", serde(try_from = "CompactString"))]
11#[repr(transparent)]
12pub struct FileExtension(CompactString);
13
14#[derive(Debug, Error, Eq, PartialEq)]
15pub enum FileExtensionError {
16 #[error("File extension contains invalid character {_0:?}")]
17 InvalidCharacter(char),
18 #[error("File extension must not be empty")]
19 Empty,
20 #[error(
21 "File extension must not have more than {} characters but has {_0}",
22 FileExtension::MAX_CHAR_LENGTH
23 )]
24 TooLong(usize),
25}
26
27impl FileExtension {
28 pub const MAX_CHAR_LENGTH: usize = 64;
29
30 pub fn new<T: AsRef<str>>(file_extension: T) -> Result<Self, FileExtensionError> {
53 let extension = file_extension.as_ref().trim_start_matches('.');
54
55 if extension.is_empty() {
56 return Err(FileExtensionError::Empty);
57 }
58
59 let char_count = extension.chars().try_fold(0, |char_count, char| {
60 if DISALLOWED_CHARACTERS.contains(&char) || char.is_control() {
61 return Err(FileExtensionError::InvalidCharacter(char));
62 }
63
64 Ok(char_count + 1)
65 })?;
66
67 if char_count > Self::MAX_CHAR_LENGTH {
68 return Err(FileExtensionError::TooLong(char_count));
69 }
70
71 Ok(Self(extension.into()))
72 }
73
74 #[must_use]
82 #[inline]
83 pub unsafe fn new_unchecked<T: Into<CompactString>>(file_extension: T) -> Self {
84 Self(file_extension.into())
85 }
86
87 #[must_use]
89 #[inline]
90 pub fn as_str(&self) -> &str {
91 self.0.as_str()
92 }
93}
94
95impl AsRef<str> for FileExtension {
96 #[inline]
97 fn as_ref(&self) -> &str {
98 self.as_str()
99 }
100}
101
102impl fmt::Display for FileExtension {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 self.0.fmt(f)
105 }
106}
107
108impl FromStr for FileExtension {
109 type Err = FileExtensionError;
110
111 #[inline]
112 fn from_str(s: &str) -> Result<Self, Self::Err> {
113 Self::new(s)
114 }
115}
116
117impl TryFrom<CompactString> for FileExtension {
118 type Error = FileExtensionError;
119
120 #[inline]
121 fn try_from(value: CompactString) -> Result<Self, Self::Error> {
122 Self::new(value)
123 }
124}