Skip to main content

quick_m3u8/
error.rs

1//! All error types exposed by the library.
2//!
3//! The module offers a collection of many error types coming from various operations.
4
5use crate::line::{ParsedByteSlice, ParsedLineSlice};
6use std::{
7    error::Error,
8    fmt::{Display, Formatter},
9    str::Utf8Error,
10};
11
12/// Error in reading a line from a [`crate::Reader`] constructed with [`crate::Reader::from_str`].
13#[derive(Debug, PartialEq, Clone)]
14pub struct ReaderStrError<'a> {
15    /// The original line that caused the error.
16    ///
17    /// The `Reader` exposes this to the user so that it can continue to the next line when
18    /// [`crate::Reader::read_line`] is called again.
19    pub errored_line: &'a str,
20    /// The underlying error that was experienced.
21    pub error: SyntaxError,
22}
23
24/// Error in reading a line from a [`crate::Reader`] constructed with [`crate::Reader::from_bytes`].
25#[derive(Debug, PartialEq, Clone)]
26pub struct ReaderBytesError<'a> {
27    /// The original line that caused the error.
28    ///
29    /// The `Reader` exposes this to the user so that it can continue to the next line when
30    /// [`crate::Reader::read_line`] is called again.
31    pub errored_line: &'a [u8],
32    /// The underlying error that was experienced.
33    pub error: SyntaxError,
34}
35
36/// Error in reading a line from [`crate::line::parse`] (or [`crate::line::parse_with_custom`]).
37#[derive(Debug, PartialEq, Clone)]
38pub struct ParseLineStrError<'a> {
39    /// The original line that caused the error along with the remaining slice after the line.
40    pub errored_line_slice: ParsedLineSlice<'a, &'a str>,
41    /// The underlying error that was experienced.
42    pub error: SyntaxError,
43}
44
45/// Error in reading a line from [`crate::line::parse_bytes`] (or
46/// [`crate::line::parse_bytes_with_custom`]).
47#[derive(Debug, PartialEq, Clone)]
48pub struct ParseLineBytesError<'a> {
49    /// The original line that caused the error along with the remaining bytes after the line.
50    pub errored_line_slice: ParsedByteSlice<'a, &'a [u8]>,
51    /// The underlying error that was experienced.
52    pub error: SyntaxError,
53}
54
55macro_rules! impl_error {
56    ($type:ident) => {
57        impl Display for $type<'_> {
58            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59                self.error.fmt(f)
60            }
61        }
62        impl Error for $type<'_> {}
63    };
64}
65impl_error!(ReaderStrError);
66impl_error!(ReaderBytesError);
67impl_error!(ParseLineStrError);
68impl_error!(ParseLineBytesError);
69
70/// Error experienced during parsing of a line.
71#[derive(Debug, PartialEq, Clone, Copy)]
72pub enum SyntaxError {
73    /// A generic syntax error that breaks parsing of the line.
74    Generic(GenericSyntaxError),
75    /// An error experienced while trying to parse [`crate::line::HlsLine::UnknownTag`].
76    UnknownTag(UnknownTagSyntaxError),
77    /// An error experienced while trying to parse a `DateTime`.
78    DateTime(DateTimeSyntaxError),
79    /// An error experienced while trying to parse a tag value.
80    TagValue(TagValueSyntaxError),
81    /// Invalid UTF-8 was encountered.
82    InvalidUtf8(Utf8Error),
83}
84
85impl Display for SyntaxError {
86    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
87        match self {
88            Self::Generic(e) => e.fmt(f),
89            Self::UnknownTag(e) => e.fmt(f),
90            Self::DateTime(e) => e.fmt(f),
91            Self::TagValue(e) => e.fmt(f),
92            Self::InvalidUtf8(e) => e.fmt(f),
93        }
94    }
95}
96impl Error for SyntaxError {}
97
98/// A generic syntax error that breaks parsing of the line.
99#[derive(Debug, PartialEq, Clone, Copy)]
100pub enum GenericSyntaxError {
101    /// Carriage return (`U+000D`) was encountered and not followed by line feed (`U+000A`).
102    CarriageReturnWithoutLineFeed,
103    /// The line ended unexpectedly (e.g. within a quoted string in an attribute list).
104    UnexpectedEndOfLine,
105    /// Some part of the line could not be decoded as UTF-8.
106    InvalidUtf8(Utf8Error),
107}
108impl Display for GenericSyntaxError {
109    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
110        match self {
111            Self::CarriageReturnWithoutLineFeed => write!(
112                f,
113                "carriage return (U+000D) without a following line feed (U+000A) is not supported"
114            ),
115            Self::UnexpectedEndOfLine => write!(f, "line ended unexpectedly during parsing"),
116            Self::InvalidUtf8(e) => write!(f, "invalid utf-8 due to {e}"),
117        }
118    }
119}
120impl Error for GenericSyntaxError {}
121impl From<GenericSyntaxError> for SyntaxError {
122    fn from(value: GenericSyntaxError) -> Self {
123        Self::Generic(value)
124    }
125}
126impl From<Utf8Error> for SyntaxError {
127    fn from(value: Utf8Error) -> Self {
128        Self::InvalidUtf8(value)
129    }
130}
131
132/// An error experienced while trying to parse [`crate::line::HlsLine::UnknownTag`].
133#[derive(Debug, PartialEq, Clone, Copy)]
134pub enum UnknownTagSyntaxError {
135    /// The tag prefix `#EXT` existed but nothing more before the line ended or the `:` character
136    /// was found.
137    UnexpectedNoTagName,
138    /// An `UnknownTag` was attempted to be parsed directly (via
139    /// [`crate::custom_parsing::tag::parse`]), but the line did not start with `#EXT`.
140    InvalidTag,
141    /// A generic syntax error that breaks parsing of the line.
142    Generic(GenericSyntaxError),
143}
144impl Display for UnknownTagSyntaxError {
145    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
146        match self {
147            Self::UnexpectedNoTagName => write!(
148                f,
149                "tag (starting with '#EXT') had no name (no more characters until new line)"
150            ),
151            Self::InvalidTag => write!(
152                f,
153                "input did not start with '#EXT' and so is not a valid tag"
154            ),
155            Self::Generic(e) => e.fmt(f),
156        }
157    }
158}
159impl Error for UnknownTagSyntaxError {}
160impl From<UnknownTagSyntaxError> for SyntaxError {
161    fn from(value: UnknownTagSyntaxError) -> Self {
162        Self::UnknownTag(value)
163    }
164}
165impl From<GenericSyntaxError> for UnknownTagSyntaxError {
166    fn from(value: GenericSyntaxError) -> Self {
167        Self::Generic(value)
168    }
169}
170impl From<Utf8Error> for UnknownTagSyntaxError {
171    fn from(value: Utf8Error) -> Self {
172        Self::Generic(GenericSyntaxError::InvalidUtf8(value))
173    }
174}
175
176#[cfg(feature = "chrono")]
177/// An error experienced while trying to parse [`chrono::DateTime`].
178#[derive(Debug, PartialEq, Clone, Copy)]
179pub enum DateTimeSyntaxError {
180    /// Some part of the line could not be decoded as UTF-8.
181    InvalidUtf8(Utf8Error),
182    /// Attempting to parse from RFC3339 failed.
183    ChronoParseError(chrono::ParseError),
184}
185#[cfg(feature = "chrono")]
186impl Display for DateTimeSyntaxError {
187    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
188        match self {
189            Self::InvalidUtf8(e) => e.fmt(f),
190            Self::ChronoParseError(e) => e.fmt(f),
191        }
192    }
193}
194#[cfg(feature = "chrono")]
195impl Error for DateTimeSyntaxError {}
196#[cfg(feature = "chrono")]
197impl From<DateTimeSyntaxError> for SyntaxError {
198    fn from(value: DateTimeSyntaxError) -> Self {
199        Self::DateTime(value)
200    }
201}
202#[cfg(feature = "chrono")]
203impl From<Utf8Error> for DateTimeSyntaxError {
204    fn from(value: Utf8Error) -> Self {
205        Self::InvalidUtf8(value)
206    }
207}
208#[cfg(feature = "chrono")]
209impl From<chrono::ParseError> for DateTimeSyntaxError {
210    fn from(value: chrono::ParseError) -> Self {
211        Self::ChronoParseError(value)
212    }
213}
214#[cfg(not(feature = "chrono"))]
215/// An error experienced while trying to parse [`crate::date::DateTime`].
216#[derive(Debug, PartialEq, Clone, Copy)]
217pub enum DateTimeSyntaxError {
218    /// The year component was not a valid number.
219    InvalidYear(ParseNumberError),
220    /// The separator between year and month was not `-`.
221    UnexpectedYearToMonthSeparator(Option<u8>),
222    /// The month component was not a valid number.
223    InvalidMonth(ParseNumberError),
224    /// The separator between month and day was not `-`.
225    UnexpectedMonthToDaySeparator(Option<u8>),
226    /// The day component was not a valid number.
227    InvalidDay(ParseNumberError),
228    /// The separator between day and hour was not `T` or `t`.
229    UnexpectedDayHourSeparator(Option<u8>),
230    /// The hour component was not a valid number.
231    InvalidHour(ParseNumberError),
232    /// The separator between hour and minute was not `:`.
233    UnexpectedHourMinuteSeparator(Option<u8>),
234    /// The minute component was not a valid number.
235    InvalidMinute(ParseNumberError),
236    /// The separator between minute and second was not `:`.
237    UnexpectedMinuteSecondSeparator(Option<u8>),
238    /// The second component was not a valid number.
239    InvalidSecond,
240    /// No timezone information was provided.
241    UnexpectedNoTimezone,
242    /// Characters existed after the timezone.
243    UnexpectedCharactersAfterTimezone,
244    /// The hour component of the timezone offset was not a valid number.
245    InvalidTimezoneHour(ParseNumberError),
246    /// The separator between hour and minute in the timezone was not `:`.
247    UnexpectedTimezoneHourMinuteSeparator(Option<u8>),
248    /// The minute component of the timezone offset was not a valid number.
249    InvalidTimezoneMinute(ParseNumberError),
250    /// A generic syntax error that breaks parsing of the line.
251    Generic(GenericSyntaxError),
252}
253#[cfg(not(feature = "chrono"))]
254fn option_u8_to_string(u: &Option<u8>) -> String {
255    u.map(|b| format!("{}", b as char))
256        .unwrap_or("None".to_string())
257}
258#[cfg(not(feature = "chrono"))]
259impl Display for DateTimeSyntaxError {
260    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
261        match self {
262            Self::InvalidYear(e) => write!(f, "invalid integer for year in date due to {e}"),
263            Self::UnexpectedYearToMonthSeparator(s) => write!(
264                f,
265                "expected '-' between year and month but was {}",
266                option_u8_to_string(s)
267            ),
268            Self::InvalidMonth(e) => write!(f, "invalid integer for month in date due to {e}"),
269            Self::UnexpectedMonthToDaySeparator(s) => write!(
270                f,
271                "expected '-' between month and day but was {}",
272                option_u8_to_string(s)
273            ),
274            Self::InvalidDay(e) => write!(f, "invalid integer for day in date due to {e}"),
275            Self::UnexpectedDayHourSeparator(s) => write!(
276                f,
277                "expected 'T' or 't' between day and hour but was {}",
278                option_u8_to_string(s)
279            ),
280            Self::InvalidHour(e) => write!(f, "invalid integer for hour in date due to {e}"),
281            Self::UnexpectedHourMinuteSeparator(s) => write!(
282                f,
283                "expected ':' between hour and minute but was {}",
284                option_u8_to_string(s)
285            ),
286            Self::InvalidMinute(e) => write!(f, "invalid integer for minute in date due to {e}"),
287            Self::UnexpectedMinuteSecondSeparator(s) => write!(
288                f,
289                "expected ':' between minute and second but was {}",
290                option_u8_to_string(s)
291            ),
292            Self::InvalidSecond => write!(f, "invalid float for second in date"),
293            Self::UnexpectedNoTimezone => write!(
294                f,
295                "no timezone in date (expect either 'Z' or full timezone)"
296            ),
297            Self::UnexpectedCharactersAfterTimezone => {
298                write!(f, "unexpected characters after timezone in date")
299            }
300            Self::InvalidTimezoneHour(e) => {
301                write!(f, "invalid integer for hour in timezone due to {e}")
302            }
303            Self::UnexpectedTimezoneHourMinuteSeparator(s) => write!(
304                f,
305                "expected ':' between hour and minute in timezone but was {}",
306                option_u8_to_string(s)
307            ),
308            Self::InvalidTimezoneMinute(e) => {
309                write!(f, "invalid integer for minute in timezone due to {e}")
310            }
311            Self::Generic(e) => e.fmt(f),
312        }
313    }
314}
315#[cfg(not(feature = "chrono"))]
316impl Error for DateTimeSyntaxError {}
317#[cfg(not(feature = "chrono"))]
318impl From<DateTimeSyntaxError> for SyntaxError {
319    fn from(value: DateTimeSyntaxError) -> Self {
320        Self::DateTime(value)
321    }
322}
323#[cfg(not(feature = "chrono"))]
324impl From<GenericSyntaxError> for DateTimeSyntaxError {
325    fn from(value: GenericSyntaxError) -> Self {
326        Self::Generic(value)
327    }
328}
329
330/// A syntax error found while trying to parse a tag value.
331#[derive(Debug, PartialEq, Clone, Copy)]
332pub enum TagValueSyntaxError {
333    /// Value was determined to be a decimal floating point but the data was not a valid float.
334    InvalidFloatForDecimalFloatingPointValue,
335    /// Some part of the value could not be decoded as UTF-8.
336    InvalidUtf8(Utf8Error),
337    /// Value was determined to be a decimal integer but the data was not a valid number.
338    InvalidDecimalInteger(ParseNumberError),
339    /// The line ended while reading an attribute name in an attribute list.
340    UnexpectedEndOfLineWhileReadingAttributeName,
341    /// No value existed for an associated attribute name in an attribute list.
342    UnexpectedEmptyAttributeValue,
343    /// The line ended while parsing a quoted string in an attribute list.
344    UnexpectedEndOfLineWithinQuotedString,
345    /// The quoted string ended and was not immediately followed by `,` or end of line.
346    UnexpectedCharacterAfterQuotedString(u8),
347    /// An attribute value contained whitespace unexpectedly.
348    UnexpectedWhitespaceInAttributeValue,
349    /// A value was determined to be a floating point but the data was not a valid float.
350    InvalidFloatInAttributeValue,
351    /// A generic syntax error that breaks parsing of the line.
352    Generic(GenericSyntaxError),
353}
354impl Display for TagValueSyntaxError {
355    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
356        match self {
357            Self::InvalidFloatForDecimalFloatingPointValue => {
358                write!(f, "invalid float for decimal float value")
359            }
360            Self::InvalidUtf8(e) => write!(f, "invalid utf-8 due to {e}"),
361            Self::InvalidDecimalInteger(e) => {
362                write!(f, "invalid integer for decimal integer value due to {e}")
363            }
364            Self::UnexpectedEndOfLineWhileReadingAttributeName => {
365                write!(f, "unexpected end of line reading attribute name")
366            }
367            Self::UnexpectedEmptyAttributeValue => {
368                write!(f, "attribute name had no value")
369            }
370            Self::UnexpectedEndOfLineWithinQuotedString => write!(
371                f,
372                "unexpected end of line within quoted string attribute value"
373            ),
374            Self::UnexpectedCharacterAfterQuotedString(c) => write!(
375                f,
376                "unexpected character '{}' after end of quoted attribute value (only ',' is valid)",
377                *c as char
378            ),
379            Self::UnexpectedWhitespaceInAttributeValue => {
380                write!(f, "unexpected whitespace in attribute value")
381            }
382            Self::InvalidFloatInAttributeValue => {
383                write!(f, "invalid float in attribute value")
384            }
385            Self::Generic(e) => e.fmt(f),
386        }
387    }
388}
389impl Error for TagValueSyntaxError {}
390impl From<TagValueSyntaxError> for SyntaxError {
391    fn from(value: TagValueSyntaxError) -> Self {
392        Self::TagValue(value)
393    }
394}
395impl From<GenericSyntaxError> for TagValueSyntaxError {
396    fn from(value: GenericSyntaxError) -> Self {
397        Self::Generic(value)
398    }
399}
400impl From<fast_float2::Error> for TagValueSyntaxError {
401    fn from(_: fast_float2::Error) -> Self {
402        Self::InvalidFloatForDecimalFloatingPointValue
403    }
404}
405impl From<ParseFloatError> for TagValueSyntaxError {
406    fn from(_: ParseFloatError) -> Self {
407        Self::InvalidFloatForDecimalFloatingPointValue
408    }
409}
410impl From<Utf8Error> for TagValueSyntaxError {
411    fn from(value: Utf8Error) -> Self {
412        Self::InvalidUtf8(value)
413    }
414}
415
416/// An error experienced while trying to convert into a known tag via `TryFrom<ParsedTag>`.
417#[derive(Debug, PartialEq, Clone, Copy)]
418pub enum ValidationError {
419    /// The tag name did not match expectations for the tag.
420    UnexpectedTagName,
421    /// A required attribute was missing (the associated value should be the required attribute
422    /// name).
423    MissingRequiredAttribute(&'static str),
424    /// Parsing for this tag is not implemented.
425    NotImplemented,
426    /// The expected value of the tag could not be obtained.
427    ErrorExtractingTagValue(ParseTagValueError),
428    /// An attribute value within an attribute list could not be parsed.
429    ErrorExtractingAttributeListValue(ParseAttributeValueError),
430    /// The enumerated string extracted from [`crate::tag::UnquotedAttributeValue`] was not a known
431    /// value.
432    InvalidEnumeratedString,
433}
434impl Display for ValidationError {
435    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
436        match self {
437            Self::UnexpectedTagName => write!(f, "unexpected tag name"),
438            Self::MissingRequiredAttribute(a) => write!(f, "required attribute {a} is missing"),
439            Self::NotImplemented => write!(f, "parsing into this tag is not implemented"),
440            Self::ErrorExtractingTagValue(e) => write!(f, "tag value error - {e}"),
441            Self::ErrorExtractingAttributeListValue(e) => {
442                write!(f, "attribute list value error - {e}")
443            }
444            Self::InvalidEnumeratedString => write!(f, "invalid enumerated string in value"),
445        }
446    }
447}
448impl Error for ValidationError {}
449impl From<ParseTagValueError> for ValidationError {
450    fn from(value: ParseTagValueError) -> Self {
451        Self::ErrorExtractingTagValue(value)
452    }
453}
454impl From<ParseNumberError> for ValidationError {
455    fn from(value: ParseNumberError) -> Self {
456        Self::ErrorExtractingTagValue(From::from(value))
457    }
458}
459impl From<ParseDecimalIntegerRangeError> for ValidationError {
460    fn from(value: ParseDecimalIntegerRangeError) -> Self {
461        Self::ErrorExtractingTagValue(From::from(value))
462    }
463}
464impl From<ParsePlaylistTypeError> for ValidationError {
465    fn from(value: ParsePlaylistTypeError) -> Self {
466        Self::ErrorExtractingTagValue(From::from(value))
467    }
468}
469impl From<ParseFloatError> for ValidationError {
470    fn from(value: ParseFloatError) -> Self {
471        Self::ErrorExtractingTagValue(From::from(value))
472    }
473}
474impl From<ParseDecimalFloatingPointWithTitleError> for ValidationError {
475    fn from(value: ParseDecimalFloatingPointWithTitleError) -> Self {
476        Self::ErrorExtractingTagValue(From::from(value))
477    }
478}
479impl From<DateTimeSyntaxError> for ValidationError {
480    fn from(value: DateTimeSyntaxError) -> Self {
481        Self::ErrorExtractingTagValue(From::from(value))
482    }
483}
484impl From<AttributeListParsingError> for ValidationError {
485    fn from(value: AttributeListParsingError) -> Self {
486        Self::ErrorExtractingTagValue(From::from(value))
487    }
488}
489impl From<ParseAttributeValueError> for ValidationError {
490    fn from(value: ParseAttributeValueError) -> Self {
491        Self::ErrorExtractingAttributeListValue(value)
492    }
493}
494
495/// An error found trying to convert a tag value into a different data type needed for the tag.
496#[derive(Debug, PartialEq, Clone, Copy)]
497pub enum ParseTagValueError {
498    /// The tag value is not empty (`None`) but it should be.
499    NotEmpty,
500    /// The tag value is empty (`None`) when it should not be.
501    UnexpectedEmpty,
502    /// An issue found trying to convert into a decimal integer.
503    DecimalInteger(ParseNumberError),
504    /// An issue fouud trying to convert into a decimal integer range (`<n>[@<o>]`).
505    DecimalIntegerRange(ParseDecimalIntegerRangeError),
506    /// An issue found trying to convert into a playlist type enum (`EVENT` or `VOD`).
507    PlaylistType(ParsePlaylistTypeError),
508    /// An issue found trying to convert into a decimal floating point number.
509    DecimalFloatingPoint(ParseFloatError),
510    /// An issue found trying to convert into a decimal floating point number with a UTF-8 title.
511    DecimalFloatingPointWithTitle(ParseDecimalFloatingPointWithTitleError),
512    /// An issue found trying to convert into a date/time.
513    DateTime(DateTimeSyntaxError),
514    /// An issue found trying to convert into an attribute list.
515    AttributeList(AttributeListParsingError),
516}
517impl Display for ParseTagValueError {
518    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
519        match self {
520            Self::NotEmpty => write!(f, "tag value was unexpectedly not empty"),
521            Self::UnexpectedEmpty => write!(f, "tag value was unexpectedly empty"),
522            Self::DecimalInteger(e) => e.fmt(f),
523            Self::DecimalIntegerRange(e) => e.fmt(f),
524            Self::PlaylistType(e) => e.fmt(f),
525            Self::DecimalFloatingPoint(e) => e.fmt(f),
526            Self::DecimalFloatingPointWithTitle(e) => e.fmt(f),
527            Self::DateTime(e) => e.fmt(f),
528            Self::AttributeList(e) => e.fmt(f),
529        }
530    }
531}
532impl Error for ParseTagValueError {}
533impl From<ParseNumberError> for ParseTagValueError {
534    fn from(value: ParseNumberError) -> Self {
535        Self::DecimalInteger(value)
536    }
537}
538impl From<ParseDecimalIntegerRangeError> for ParseTagValueError {
539    fn from(value: ParseDecimalIntegerRangeError) -> Self {
540        Self::DecimalIntegerRange(value)
541    }
542}
543impl From<ParsePlaylistTypeError> for ParseTagValueError {
544    fn from(value: ParsePlaylistTypeError) -> Self {
545        Self::PlaylistType(value)
546    }
547}
548impl From<ParseFloatError> for ParseTagValueError {
549    fn from(value: ParseFloatError) -> Self {
550        Self::DecimalFloatingPoint(value)
551    }
552}
553impl From<ParseDecimalFloatingPointWithTitleError> for ParseTagValueError {
554    fn from(value: ParseDecimalFloatingPointWithTitleError) -> Self {
555        Self::DecimalFloatingPointWithTitle(value)
556    }
557}
558impl From<DateTimeSyntaxError> for ParseTagValueError {
559    fn from(value: DateTimeSyntaxError) -> Self {
560        Self::DateTime(value)
561    }
562}
563impl From<AttributeListParsingError> for ParseTagValueError {
564    fn from(value: AttributeListParsingError) -> Self {
565        Self::AttributeList(value)
566    }
567}
568
569/// An error in trying to convert into a decimal float with a title (used in the `EXTINF` tag).
570#[derive(Debug, PartialEq, Clone, Copy)]
571pub enum ParseDecimalFloatingPointWithTitleError {
572    /// The duration is not a valid number.
573    InvalidDuration(ParseFloatError),
574    /// The title is not valid UTF-8.
575    InvalidTitle(Utf8Error),
576}
577impl Display for ParseDecimalFloatingPointWithTitleError {
578    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
579        match self {
580            Self::InvalidDuration(e) => write!(f, "invalid duration due to {e}"),
581            Self::InvalidTitle(e) => write!(f, "invalid title due to {e}"),
582        }
583    }
584}
585impl Error for ParseDecimalFloatingPointWithTitleError {}
586impl From<ParseFloatError> for ParseDecimalFloatingPointWithTitleError {
587    fn from(value: ParseFloatError) -> Self {
588        Self::InvalidDuration(value)
589    }
590}
591impl From<Utf8Error> for ParseDecimalFloatingPointWithTitleError {
592    fn from(value: Utf8Error) -> Self {
593        Self::InvalidTitle(value)
594    }
595}
596impl From<fast_float2::Error> for ParseDecimalFloatingPointWithTitleError {
597    fn from(_: fast_float2::Error) -> Self {
598        Self::InvalidDuration(ParseFloatError)
599    }
600}
601
602/// An error in trying to convert an attribute value into a different data type as defined for the
603/// attribute in the HLS specification.
604#[derive(Debug, PartialEq, Clone, Copy)]
605pub enum ParseAttributeValueError {
606    /// The value is wrapped in quotes when it should not be.
607    UnexpectedQuoted {
608        /// The name of the attribute.
609        attr_name: &'static str,
610    },
611    /// The value is not wrapped in quotes when it should be.
612    UnexpectedUnquoted {
613        /// The name of the attribute.
614        attr_name: &'static str,
615    },
616    /// An issue found trying to convert into a decimal integer.
617    DecimalInteger {
618        /// The name of the attribute.
619        attr_name: &'static str,
620        /// The underlying error.
621        error: ParseNumberError,
622    },
623    /// An issue found trying to convert into a decimal floating point.
624    DecimalFloatingPoint {
625        /// The name of the attribute.
626        attr_name: &'static str,
627        /// The underlying error.
628        error: ParseFloatError,
629    },
630    /// An issue found trying to convert into a decimal resolution.
631    DecimalResolution {
632        /// The name of the attribute.
633        attr_name: &'static str,
634        /// The underlying error.
635        error: DecimalResolutionParseError,
636    },
637    /// An issue found trying to convert into a UTF-8 string.
638    Utf8 {
639        /// The name of the attribute.
640        attr_name: &'static str,
641        /// The underlying error.
642        error: Utf8Error,
643    },
644}
645impl Display for ParseAttributeValueError {
646    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
647        match self {
648            Self::UnexpectedQuoted { attr_name } => {
649                write!(f, "{attr_name} expected to be unquoted but was quoted")
650            }
651            Self::UnexpectedUnquoted { attr_name } => {
652                write!(f, "{attr_name} expected to be quoted but was unquoted")
653            }
654            Self::DecimalInteger { attr_name, error } => write!(
655                f,
656                "could not extract decimal integer for {attr_name} due to {error}"
657            ),
658            Self::DecimalFloatingPoint { attr_name, error } => write!(
659                f,
660                "could not extract decimal floating point for {attr_name} due to {error}"
661            ),
662            Self::DecimalResolution { attr_name, error } => write!(
663                f,
664                "could not extract decimal resolution for {attr_name} due to {error}"
665            ),
666            Self::Utf8 { attr_name, error } => write!(
667                f,
668                "could not extract utf-8 string for {attr_name} due to {error}"
669            ),
670        }
671    }
672}
673impl Error for ParseAttributeValueError {}
674
675/// An error found while trying to parse a number.
676#[derive(Debug, PartialEq, Clone, Copy)]
677pub enum ParseNumberError {
678    /// An invalid digit was found.
679    InvalidDigit(u8),
680    /// The number was too big.
681    NumberTooBig,
682    /// Empty data was found instead of a number.
683    Empty,
684}
685impl Display for ParseNumberError {
686    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
687        match self {
688            Self::InvalidDigit(got) => write!(f, "invalid digit {got}"),
689            Self::NumberTooBig => write!(f, "number is too big"),
690            Self::Empty => write!(f, "cannot parse number from empty slice"),
691        }
692    }
693}
694impl Error for ParseNumberError {}
695
696/// An error when trying to parse the playlist type
697#[derive(Debug, PartialEq, Clone, Copy)]
698pub enum ParsePlaylistTypeError {
699    /// The value provided was not `VOD` or `EVENT`.
700    InvalidValue,
701}
702impl Display for ParsePlaylistTypeError {
703    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
704        match self {
705            Self::InvalidValue => write!(f, "expected 'EVENT' or 'VOD'"),
706        }
707    }
708}
709impl Error for ParsePlaylistTypeError {}
710
711/// An error when trying to parse a decimal integer range (`<n>[@<o>]`).
712#[derive(Debug, PartialEq, Clone, Copy)]
713pub enum ParseDecimalIntegerRangeError {
714    /// The length component was not a valid number.
715    InvalidLength(ParseNumberError),
716    /// The offset component was not a valid number.
717    InvalidOffset(ParseNumberError),
718}
719impl Display for ParseDecimalIntegerRangeError {
720    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
721        match self {
722            Self::InvalidLength(e) => write!(f, "invalid length due to {e}"),
723            Self::InvalidOffset(e) => write!(f, "invalid offset due to {e}"),
724        }
725    }
726}
727impl Error for ParseDecimalIntegerRangeError {}
728
729/// An error when trying to parse the `EXT-X-MAP:BYTERANGE` attribute.
730#[derive(Debug, PartialEq, Clone, Copy)]
731pub enum ParseMapByterangeError {
732    /// An error parsing the decimal integer range.
733    RangeParseError(ParseDecimalIntegerRangeError),
734    /// The offset component was missing but it is needed for `MapByterange`.
735    MissingOffset,
736}
737impl Display for ParseMapByterangeError {
738    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
739        match self {
740            Self::RangeParseError(e) => e.fmt(f),
741            Self::MissingOffset => write!(f, "missing offset component"),
742        }
743    }
744}
745impl Error for ParseMapByterangeError {}
746impl From<ParseDecimalIntegerRangeError> for ParseMapByterangeError {
747    fn from(value: ParseDecimalIntegerRangeError) -> Self {
748        Self::RangeParseError(value)
749    }
750}
751
752/// An error found when trying to parse a float from a byte slice (`&[u8]`).
753#[derive(Debug, PartialEq, Clone, Copy)]
754pub struct ParseFloatError;
755impl Display for ParseFloatError {
756    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
757        write!(f, "invalid float")
758    }
759}
760impl Error for ParseFloatError {}
761
762/// An enumerated string provided to a parsed tag was not recognized.
763#[derive(Debug, PartialEq, Clone)]
764pub struct UnrecognizedEnumerationError<'a> {
765    /// The unrecognized value that was found.
766    pub value: &'a str,
767}
768impl<'a> UnrecognizedEnumerationError<'a> {
769    /// Construct a new instance of the error using the unrecognized value that was found.
770    pub fn new(value: &'a str) -> Self {
771        Self { value }
772    }
773}
774impl<'a> Display for UnrecognizedEnumerationError<'a> {
775    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
776        write!(f, "{} is not a recognized enumeration", self.value)
777    }
778}
779impl Error for UnrecognizedEnumerationError<'_> {}
780
781/// An error found when trying to parse a decimal resolution (`<width>x<height>`).
782#[derive(Debug, PartialEq, Clone, Copy)]
783pub enum DecimalResolutionParseError {
784    /// The width component was not a valid integer.
785    InvalidWidth,
786    /// The `x` separator character was missing.
787    MissingSeparator,
788    /// The height component was not a valid integer.
789    InvalidHeight,
790}
791impl Display for DecimalResolutionParseError {
792    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
793        match self {
794            Self::InvalidWidth => write!(f, "not a number for width"),
795            Self::MissingSeparator => write!(f, "missing `x` separator"),
796            Self::InvalidHeight => write!(f, "not a number for height"),
797        }
798    }
799}
800impl Error for DecimalResolutionParseError {}
801
802/// An error found while parsing an attribute list.
803#[derive(Debug, PartialEq, Clone, Copy)]
804pub enum AttributeListParsingError {
805    /// The line ended while reading an attribute name (before we found `=`).
806    EndOfLineWhileReadingAttributeName,
807    /// There was an unexpected character (`"` or `,`) in the attribute name.
808    UnexpectedCharacterInAttributeName,
809    /// The attribute name was empty (no characters between start and `=`).
810    EmptyAttributeName,
811    /// The unquoted attribute value was empty (no characters between start and `,` or end of line).
812    EmptyUnquotedValue,
813    /// Unexpected character in unquoted attribute value (`=` or `"` after initial index).
814    UnexpectedCharacterInAttributeValue,
815    /// Unexpected character occurring after quote end and before `,`.
816    UnexpectedCharacterAfterQuoteEnd,
817    /// The line ended while reading a quoted string value.
818    EndOfLineWhileReadingQuotedValue,
819    /// There was an error when trying to convert to UTF-8.
820    InvalidUtf8(std::str::Utf8Error),
821}
822impl Display for AttributeListParsingError {
823    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
824        match self {
825            Self::EndOfLineWhileReadingAttributeName => {
826                write!(f, "line ended while reading attribute name")
827            }
828            Self::UnexpectedCharacterInAttributeName => {
829                write!(f, "unexpected character in attribute name")
830            }
831            Self::EmptyAttributeName => write!(f, "attribute name with no characters"),
832            Self::EmptyUnquotedValue => write!(f, "unquoted value with no characters"),
833            Self::UnexpectedCharacterInAttributeValue => {
834                write!(f, "unexpected character in attribute value")
835            }
836            Self::UnexpectedCharacterAfterQuoteEnd => {
837                write!(f, "unexpected character between quoted string end and ','")
838            }
839            Self::EndOfLineWhileReadingQuotedValue => {
840                write!(f, "line ended while reading quoted string value")
841            }
842            Self::InvalidUtf8(e) => write!(f, "invalid utf-8 due to {e}"),
843        }
844    }
845}
846impl std::error::Error for AttributeListParsingError {}
847impl From<std::str::Utf8Error> for AttributeListParsingError {
848    fn from(value: std::str::Utf8Error) -> Self {
849        Self::InvalidUtf8(value)
850    }
851}