1use core::{
2 fmt::{self, Debug, Display},
3 num::ParseFloatError,
4 str::FromStr
5};
6
7use crate::xml::TrustedNoEscape;
8
9#[derive(Debug, PartialEq)]
10pub enum TimeDesignationError {
11 BadUnit,
12 BadLength,
13 Negative,
14 ParseFloat(ParseFloatError)
15}
16
17impl Display for TimeDesignationError {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 match self {
20 TimeDesignationError::BadUnit => f.write_str("time designation has invalid unit (allowed are ms, s)"),
21 TimeDesignationError::BadLength => f.write_str("string is too short to be a valid time designation"),
22 TimeDesignationError::Negative => f.write_str("time designations cannot be negative"),
23 TimeDesignationError::ParseFloat(e) => f.write_fmt(format_args!("couldn't parse float: {e}"))
24 }
25 }
26}
27
28#[cfg(feature = "std")]
29impl std::error::Error for TimeDesignationError {}
30
31#[derive(Default, Clone, PartialEq, PartialOrd)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51pub struct TimeDesignation {
52 millis: f32
53}
54
55impl TimeDesignation {
56 pub fn from_millis(millis: f32) -> Self {
58 Self { millis }
59 }
60
61 pub fn to_millis(&self) -> f32 {
63 self.millis
64 }
65}
66
67impl FromStr for TimeDesignation {
68 type Err = TimeDesignationError;
69
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 let len = s.len();
72 if len < 2 {
73 return Err(TimeDesignationError::BadLength);
74 }
75
76 let (unit, skip) = if s.ends_with("ms") {
77 (1., 2)
78 } else if s.ends_with('s') && matches!(s.chars().nth(len - 2), Some('0'..='9') | Some('.')) {
79 (1000., 1)
80 } else {
81 return Err(TimeDesignationError::BadUnit);
82 };
83
84 let f = s[..len - skip].parse::<f32>().map_err(TimeDesignationError::ParseFloat)?;
85 if f.is_sign_negative() {
86 return Err(TimeDesignationError::Negative);
87 }
88
89 Ok(Self::from_millis(f * unit))
90 }
91}
92
93impl From<&str> for TimeDesignation {
94 fn from(value: &str) -> Self {
95 value.parse().unwrap_or_default()
96 }
97}
98
99impl Display for TimeDesignation {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 f.write_fmt(format_args!("{:+}ms", self.to_millis()))
102 }
103}
104impl TrustedNoEscape for TimeDesignation {}
105
106impl Debug for TimeDesignation {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 Display::fmt(self, f)
109 }
110}
111
112#[derive(Debug, PartialEq)]
113pub enum DecibelsError {
114 BadUnit,
115 BadLength,
116 ParseFloat(ParseFloatError)
117}
118
119impl Display for DecibelsError {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 match self {
122 DecibelsError::BadUnit => f.write_str("decibels has invalid unit (allowed are dB)"),
123 DecibelsError::BadLength => f.write_str("string is too short to be a valid decibel value"),
124 DecibelsError::ParseFloat(e) => f.write_fmt(format_args!("couldn't parse float: {e}"))
125 }
126 }
127}
128
129#[cfg(feature = "std")]
130impl std::error::Error for DecibelsError {}
131
132#[derive(Default, Clone, PartialEq, PartialOrd)]
149#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
150pub struct Decibels(f32);
151
152impl Decibels {
153 pub fn new(value: f32) -> Self {
154 Self(value)
155 }
156
157 pub fn value(&self) -> f32 {
158 self.0
159 }
160}
161
162impl FromStr for Decibels {
163 type Err = DecibelsError;
164
165 fn from_str(s: &str) -> Result<Self, Self::Err> {
166 let len = s.len();
167 if len < 2 {
168 return Err(DecibelsError::BadLength);
169 }
170
171 if !s.ends_with("dB") {
172 return Err(DecibelsError::BadUnit);
173 }
174
175 let f = s[..len - 2].parse::<f32>().map_err(DecibelsError::ParseFloat)?;
176 Ok(Self(f))
177 }
178}
179
180impl From<f32> for Decibels {
181 fn from(value: f32) -> Self {
182 Decibels(value)
183 }
184}
185
186impl From<&str> for Decibels {
187 fn from(value: &str) -> Self {
188 value.parse().unwrap_or_default()
189 }
190}
191
192impl Display for Decibels {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 f.write_fmt(format_args!("{:+}dB", self.0))
195 }
196}
197impl TrustedNoEscape for Decibels {}
198
199impl Debug for Decibels {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 Display::fmt(self, f)
202 }
203}
204
205pub(crate) struct SpeedFormatter(pub(crate) f32);
206impl Display for SpeedFormatter {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 f.write_fmt(format_args!("{}%", self.0 * 100.))
209 }
210}
211impl TrustedNoEscape for SpeedFormatter {}
212
213#[cfg(test)]
214mod tests {
215 use super::{Decibels, TimeDesignation};
216
217 #[test]
218 fn parse_time_designation() {
219 assert_eq!("+7s".parse::<TimeDesignation>(), Ok(TimeDesignation::from_millis(7000.0)));
220 assert_eq!("700ms".parse::<TimeDesignation>(), Ok(TimeDesignation::from_millis(700.0)));
221 assert!("-.7s".parse::<TimeDesignation>().is_err());
222 }
223
224 #[test]
225 fn parse_decibels() {
226 assert_eq!("+6dB".parse::<Decibels>(), Ok(Decibels(6.0)));
227 assert_eq!("-.6dB".parse::<Decibels>(), Ok(Decibels(-0.6)));
228 assert!("6".parse::<Decibels>().is_err());
229 assert!("6db".parse::<Decibels>().is_err());
230 }
231}