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 [`crate::date::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/// An error experienced while trying to parse [`crate::date::DateTime`].
177#[derive(Debug, PartialEq, Clone, Copy)]
178pub enum DateTimeSyntaxError {
179    /// The year component was not a valid number.
180    InvalidYear(ParseNumberError),
181    /// The separator between year and month was not `-`.
182    UnexpectedYearToMonthSeparator(Option<u8>),
183    /// The month component was not a valid number.
184    InvalidMonth(ParseNumberError),
185    /// The separator between month and day was not `-`.
186    UnexpectedMonthToDaySeparator(Option<u8>),
187    /// The day component was not a valid number.
188    InvalidDay(ParseNumberError),
189    /// The separator between day and hour was not `T` or `t`.
190    UnexpectedDayHourSeparator(Option<u8>),
191    /// The hour component was not a valid number.
192    InvalidHour(ParseNumberError),
193    /// The separator between hour and minute was not `:`.
194    UnexpectedHourMinuteSeparator(Option<u8>),
195    /// The minute component was not a valid number.
196    InvalidMinute(ParseNumberError),
197    /// The separator between minute and second was not `:`.
198    UnexpectedMinuteSecondSeparator(Option<u8>),
199    /// The second component was not a valid number.
200    InvalidSecond,
201    /// No timezone information was provided.
202    UnexpectedNoTimezone,
203    /// Characters existed after the timezone.
204    UnexpectedCharactersAfterTimezone,
205    /// The hour component of the timezone offset was not a valid number.
206    InvalidTimezoneHour(ParseNumberError),
207    /// The separator between hour and minute in the timezone was not `:`.
208    UnexpectedTimezoneHourMinuteSeparator(Option<u8>),
209    /// The minute component of the timezone offset was not a valid number.
210    InvalidTimezoneMinute(ParseNumberError),
211    /// A generic syntax error that breaks parsing of the line.
212    Generic(GenericSyntaxError),
213}
214fn option_u8_to_string(u: &Option<u8>) -> String {
215    u.map(|b| format!("{}", b as char))
216        .unwrap_or("None".to_string())
217}
218impl Display for DateTimeSyntaxError {
219    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
220        match self {
221            Self::InvalidYear(e) => write!(f, "invalid integer for year in date due to {e}"),
222            Self::UnexpectedYearToMonthSeparator(s) => write!(
223                f,
224                "expected '-' between year and month but was {}",
225                option_u8_to_string(s)
226            ),
227            Self::InvalidMonth(e) => write!(f, "invalid integer for month in date due to {e}"),
228            Self::UnexpectedMonthToDaySeparator(s) => write!(
229                f,
230                "expected '-' between month and day but was {}",
231                option_u8_to_string(s)
232            ),
233            Self::InvalidDay(e) => write!(f, "invalid integer for day in date due to {e}"),
234            Self::UnexpectedDayHourSeparator(s) => write!(
235                f,
236                "expected 'T' or 't' between day and hour but was {}",
237                option_u8_to_string(s)
238            ),
239            Self::InvalidHour(e) => write!(f, "invalid integer for hour in date due to {e}"),
240            Self::UnexpectedHourMinuteSeparator(s) => write!(
241                f,
242                "expected ':' between hour and minute but was {}",
243                option_u8_to_string(s)
244            ),
245            Self::InvalidMinute(e) => write!(f, "invalid integer for minute in date due to {e}"),
246            Self::UnexpectedMinuteSecondSeparator(s) => write!(
247                f,
248                "expected ':' between minute and second but was {}",
249                option_u8_to_string(s)
250            ),
251            Self::InvalidSecond => write!(f, "invalid float for second in date"),
252            Self::UnexpectedNoTimezone => write!(
253                f,
254                "no timezone in date (expect either 'Z' or full timezone)"
255            ),
256            Self::UnexpectedCharactersAfterTimezone => {
257                write!(f, "unexpected characters after timezone in date")
258            }
259            Self::InvalidTimezoneHour(e) => {
260                write!(f, "invalid integer for hour in timezone due to {e}")
261            }
262            Self::UnexpectedTimezoneHourMinuteSeparator(s) => write!(
263                f,
264                "expected ':' between hour and minute in timezone but was {}",
265                option_u8_to_string(s)
266            ),
267            Self::InvalidTimezoneMinute(e) => {
268                write!(f, "invalid integer for minute in timezone due to {e}")
269            }
270            Self::Generic(e) => e.fmt(f),
271        }
272    }
273}
274impl Error for DateTimeSyntaxError {}
275impl From<DateTimeSyntaxError> for SyntaxError {
276    fn from(value: DateTimeSyntaxError) -> Self {
277        Self::DateTime(value)
278    }
279}
280impl From<GenericSyntaxError> for DateTimeSyntaxError {
281    fn from(value: GenericSyntaxError) -> Self {
282        Self::Generic(value)
283    }
284}
285
286/// A syntax error found while trying to parse a tag value.
287#[derive(Debug, PartialEq, Clone, Copy)]
288pub enum TagValueSyntaxError {
289    /// Value was determined to be a decimal floating point but the data was not a valid float.
290    InvalidFloatForDecimalFloatingPointValue,
291    /// Some part of the value could not be decoded as UTF-8.
292    InvalidUtf8(Utf8Error),
293    /// Value was determined to be a decimal integer but the data was not a valid number.
294    InvalidDecimalInteger(ParseNumberError),
295    /// The line ended while reading an attribute name in an attribute list.
296    UnexpectedEndOfLineWhileReadingAttributeName,
297    /// No value existed for an associated attribute name in an attribute list.
298    UnexpectedEmptyAttributeValue,
299    /// The line ended while parsing a quoted string in an attribute list.
300    UnexpectedEndOfLineWithinQuotedString,
301    /// The quoted string ended and was not immediately followed by `,` or end of line.
302    UnexpectedCharacterAfterQuotedString(u8),
303    /// An attribute value contained whitespace unexpectedly.
304    UnexpectedWhitespaceInAttributeValue,
305    /// A value was determined to be a floating point but the data was not a valid float.
306    InvalidFloatInAttributeValue,
307    /// A generic syntax error that breaks parsing of the line.
308    Generic(GenericSyntaxError),
309}
310impl Display for TagValueSyntaxError {
311    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
312        match self {
313            Self::InvalidFloatForDecimalFloatingPointValue => {
314                write!(f, "invalid float for decimal float value")
315            }
316            Self::InvalidUtf8(e) => write!(f, "invalid utf-8 due to {e}"),
317            Self::InvalidDecimalInteger(e) => {
318                write!(f, "invalid integer for decimal integer value due to {e}")
319            }
320            Self::UnexpectedEndOfLineWhileReadingAttributeName => {
321                write!(f, "unexpected end of line reading attribute name")
322            }
323            Self::UnexpectedEmptyAttributeValue => {
324                write!(f, "attribute name had no value")
325            }
326            Self::UnexpectedEndOfLineWithinQuotedString => write!(
327                f,
328                "unexpected end of line within quoted string attribute value"
329            ),
330            Self::UnexpectedCharacterAfterQuotedString(c) => write!(
331                f,
332                "unexpected character '{}' after end of quoted attribute value (only ',' is valid)",
333                *c as char
334            ),
335            Self::UnexpectedWhitespaceInAttributeValue => {
336                write!(f, "unexpected whitespace in attribute value")
337            }
338            Self::InvalidFloatInAttributeValue => {
339                write!(f, "invalid float in attribute value")
340            }
341            Self::Generic(e) => e.fmt(f),
342        }
343    }
344}
345impl Error for TagValueSyntaxError {}
346impl From<TagValueSyntaxError> for SyntaxError {
347    fn from(value: TagValueSyntaxError) -> Self {
348        Self::TagValue(value)
349    }
350}
351impl From<GenericSyntaxError> for TagValueSyntaxError {
352    fn from(value: GenericSyntaxError) -> Self {
353        Self::Generic(value)
354    }
355}
356impl From<fast_float2::Error> for TagValueSyntaxError {
357    fn from(_: fast_float2::Error) -> Self {
358        Self::InvalidFloatForDecimalFloatingPointValue
359    }
360}
361impl From<ParseFloatError> for TagValueSyntaxError {
362    fn from(_: ParseFloatError) -> Self {
363        Self::InvalidFloatForDecimalFloatingPointValue
364    }
365}
366impl From<Utf8Error> for TagValueSyntaxError {
367    fn from(value: Utf8Error) -> Self {
368        Self::InvalidUtf8(value)
369    }
370}
371
372/// An error experienced while trying to convert into a known tag via `TryFrom<ParsedTag>`.
373#[derive(Debug, PartialEq, Clone, Copy)]
374pub enum ValidationError {
375    /// The tag name did not match expectations for the tag.
376    UnexpectedTagName,
377    /// A required attribute was missing (the associated value should be the required attribute
378    /// name).
379    MissingRequiredAttribute(&'static str),
380    /// Parsing for this tag is not implemented.
381    NotImplemented,
382    /// The expected value of the tag could not be obtained.
383    ErrorExtractingTagValue(ParseTagValueError),
384    /// An attribute value within an attribute list could not be parsed.
385    ErrorExtractingAttributeListValue(ParseAttributeValueError),
386    /// The enumerated string extracted from [`crate::tag::UnquotedAttributeValue`] was not a known
387    /// value.
388    InvalidEnumeratedString,
389}
390impl Display for ValidationError {
391    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
392        match self {
393            Self::UnexpectedTagName => write!(f, "unexpected tag name"),
394            Self::MissingRequiredAttribute(a) => write!(f, "required attribute {a} is missing"),
395            Self::NotImplemented => write!(f, "parsing into this tag is not implemented"),
396            Self::ErrorExtractingTagValue(e) => write!(f, "tag value error - {e}"),
397            Self::ErrorExtractingAttributeListValue(e) => {
398                write!(f, "attribute list value error - {e}")
399            }
400            Self::InvalidEnumeratedString => write!(f, "invalid enumerated string in value"),
401        }
402    }
403}
404impl Error for ValidationError {}
405impl From<ParseTagValueError> for ValidationError {
406    fn from(value: ParseTagValueError) -> Self {
407        Self::ErrorExtractingTagValue(value)
408    }
409}
410impl From<ParseNumberError> for ValidationError {
411    fn from(value: ParseNumberError) -> Self {
412        Self::ErrorExtractingTagValue(From::from(value))
413    }
414}
415impl From<ParseDecimalIntegerRangeError> for ValidationError {
416    fn from(value: ParseDecimalIntegerRangeError) -> Self {
417        Self::ErrorExtractingTagValue(From::from(value))
418    }
419}
420impl From<ParsePlaylistTypeError> for ValidationError {
421    fn from(value: ParsePlaylistTypeError) -> Self {
422        Self::ErrorExtractingTagValue(From::from(value))
423    }
424}
425impl From<ParseFloatError> for ValidationError {
426    fn from(value: ParseFloatError) -> Self {
427        Self::ErrorExtractingTagValue(From::from(value))
428    }
429}
430impl From<ParseDecimalFloatingPointWithTitleError> for ValidationError {
431    fn from(value: ParseDecimalFloatingPointWithTitleError) -> Self {
432        Self::ErrorExtractingTagValue(From::from(value))
433    }
434}
435impl From<DateTimeSyntaxError> for ValidationError {
436    fn from(value: DateTimeSyntaxError) -> Self {
437        Self::ErrorExtractingTagValue(From::from(value))
438    }
439}
440impl From<AttributeListParsingError> for ValidationError {
441    fn from(value: AttributeListParsingError) -> Self {
442        Self::ErrorExtractingTagValue(From::from(value))
443    }
444}
445impl From<ParseAttributeValueError> for ValidationError {
446    fn from(value: ParseAttributeValueError) -> Self {
447        Self::ErrorExtractingAttributeListValue(value)
448    }
449}
450
451/// An error found trying to convert a tag value into a different data type needed for the tag.
452#[derive(Debug, PartialEq, Clone, Copy)]
453pub enum ParseTagValueError {
454    /// The tag value is not empty (`None`) but it should be.
455    NotEmpty,
456    /// The tag value is empty (`None`) when it should not be.
457    UnexpectedEmpty,
458    /// An issue found trying to convert into a decimal integer.
459    DecimalInteger(ParseNumberError),
460    /// An issue fouud trying to convert into a decimal integer range (`<n>[@<o>]`).
461    DecimalIntegerRange(ParseDecimalIntegerRangeError),
462    /// An issue found trying to convert into a playlist type enum (`EVENT` or `VOD`).
463    PlaylistType(ParsePlaylistTypeError),
464    /// An issue found trying to convert into a decimal floating point number.
465    DecimalFloatingPoint(ParseFloatError),
466    /// An issue found trying to convert into a decimal floating point number with a UTF-8 title.
467    DecimalFloatingPointWithTitle(ParseDecimalFloatingPointWithTitleError),
468    /// An issue found trying to convert into a date/time.
469    DateTime(DateTimeSyntaxError),
470    /// An issue found trying to convert into an attribute list.
471    AttributeList(AttributeListParsingError),
472}
473impl Display for ParseTagValueError {
474    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
475        match self {
476            Self::NotEmpty => write!(f, "tag value was unexpectedly not empty"),
477            Self::UnexpectedEmpty => write!(f, "tag value was unexpectedly empty"),
478            Self::DecimalInteger(e) => e.fmt(f),
479            Self::DecimalIntegerRange(e) => e.fmt(f),
480            Self::PlaylistType(e) => e.fmt(f),
481            Self::DecimalFloatingPoint(e) => e.fmt(f),
482            Self::DecimalFloatingPointWithTitle(e) => e.fmt(f),
483            Self::DateTime(e) => e.fmt(f),
484            Self::AttributeList(e) => e.fmt(f),
485        }
486    }
487}
488impl Error for ParseTagValueError {}
489impl From<ParseNumberError> for ParseTagValueError {
490    fn from(value: ParseNumberError) -> Self {
491        Self::DecimalInteger(value)
492    }
493}
494impl From<ParseDecimalIntegerRangeError> for ParseTagValueError {
495    fn from(value: ParseDecimalIntegerRangeError) -> Self {
496        Self::DecimalIntegerRange(value)
497    }
498}
499impl From<ParsePlaylistTypeError> for ParseTagValueError {
500    fn from(value: ParsePlaylistTypeError) -> Self {
501        Self::PlaylistType(value)
502    }
503}
504impl From<ParseFloatError> for ParseTagValueError {
505    fn from(value: ParseFloatError) -> Self {
506        Self::DecimalFloatingPoint(value)
507    }
508}
509impl From<ParseDecimalFloatingPointWithTitleError> for ParseTagValueError {
510    fn from(value: ParseDecimalFloatingPointWithTitleError) -> Self {
511        Self::DecimalFloatingPointWithTitle(value)
512    }
513}
514impl From<DateTimeSyntaxError> for ParseTagValueError {
515    fn from(value: DateTimeSyntaxError) -> Self {
516        Self::DateTime(value)
517    }
518}
519impl From<AttributeListParsingError> for ParseTagValueError {
520    fn from(value: AttributeListParsingError) -> Self {
521        Self::AttributeList(value)
522    }
523}
524
525/// An error in trying to convert into a decimal float with a title (used in the `EXTINF` tag).
526#[derive(Debug, PartialEq, Clone, Copy)]
527pub enum ParseDecimalFloatingPointWithTitleError {
528    /// The duration is not a valid number.
529    InvalidDuration(ParseFloatError),
530    /// The title is not valid UTF-8.
531    InvalidTitle(Utf8Error),
532}
533impl Display for ParseDecimalFloatingPointWithTitleError {
534    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
535        match self {
536            Self::InvalidDuration(e) => write!(f, "invalid duration due to {e}"),
537            Self::InvalidTitle(e) => write!(f, "invalid title due to {e}"),
538        }
539    }
540}
541impl Error for ParseDecimalFloatingPointWithTitleError {}
542impl From<ParseFloatError> for ParseDecimalFloatingPointWithTitleError {
543    fn from(value: ParseFloatError) -> Self {
544        Self::InvalidDuration(value)
545    }
546}
547impl From<Utf8Error> for ParseDecimalFloatingPointWithTitleError {
548    fn from(value: Utf8Error) -> Self {
549        Self::InvalidTitle(value)
550    }
551}
552impl From<fast_float2::Error> for ParseDecimalFloatingPointWithTitleError {
553    fn from(_: fast_float2::Error) -> Self {
554        Self::InvalidDuration(ParseFloatError)
555    }
556}
557
558/// An error in trying to convert an attribute value into a different data type as defined for the
559/// attribute in the HLS specification.
560#[derive(Debug, PartialEq, Clone, Copy)]
561pub enum ParseAttributeValueError {
562    /// The value is wrapped in quotes when it should not be.
563    UnexpectedQuoted {
564        /// The name of the attribute.
565        attr_name: &'static str,
566    },
567    /// The value is not wrapped in quotes when it should be.
568    UnexpectedUnquoted {
569        /// The name of the attribute.
570        attr_name: &'static str,
571    },
572    /// An issue found trying to convert into a decimal integer.
573    DecimalInteger {
574        /// The name of the attribute.
575        attr_name: &'static str,
576        /// The underlying error.
577        error: ParseNumberError,
578    },
579    /// An issue found trying to convert into a decimal floating point.
580    DecimalFloatingPoint {
581        /// The name of the attribute.
582        attr_name: &'static str,
583        /// The underlying error.
584        error: ParseFloatError,
585    },
586    /// An issue found trying to convert into a decimal resolution.
587    DecimalResolution {
588        /// The name of the attribute.
589        attr_name: &'static str,
590        /// The underlying error.
591        error: DecimalResolutionParseError,
592    },
593    /// An issue found trying to convert into a UTF-8 string.
594    Utf8 {
595        /// The name of the attribute.
596        attr_name: &'static str,
597        /// The underlying error.
598        error: Utf8Error,
599    },
600}
601impl Display for ParseAttributeValueError {
602    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
603        match self {
604            Self::UnexpectedQuoted { attr_name } => {
605                write!(f, "{attr_name} expected to be unquoted but was quoted")
606            }
607            Self::UnexpectedUnquoted { attr_name } => {
608                write!(f, "{attr_name} expected to be quoted but was unquoted")
609            }
610            Self::DecimalInteger { attr_name, error } => write!(
611                f,
612                "could not extract decimal integer for {attr_name} due to {error}"
613            ),
614            Self::DecimalFloatingPoint { attr_name, error } => write!(
615                f,
616                "could not extract decimal floating point for {attr_name} due to {error}"
617            ),
618            Self::DecimalResolution { attr_name, error } => write!(
619                f,
620                "could not extract decimal resolution for {attr_name} due to {error}"
621            ),
622            Self::Utf8 { attr_name, error } => write!(
623                f,
624                "could not extract utf-8 string for {attr_name} due to {error}"
625            ),
626        }
627    }
628}
629impl Error for ParseAttributeValueError {}
630
631/// An error found while trying to parse a number.
632#[derive(Debug, PartialEq, Clone, Copy)]
633pub enum ParseNumberError {
634    /// An invalid digit was found.
635    InvalidDigit(u8),
636    /// The number was too big.
637    NumberTooBig,
638    /// Empty data was found instead of a number.
639    Empty,
640}
641impl Display for ParseNumberError {
642    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
643        match self {
644            Self::InvalidDigit(got) => write!(f, "invalid digit {got}"),
645            Self::NumberTooBig => write!(f, "number is too big"),
646            Self::Empty => write!(f, "cannot parse number from empty slice"),
647        }
648    }
649}
650impl Error for ParseNumberError {}
651
652/// An error when trying to parse the playlist type
653#[derive(Debug, PartialEq, Clone, Copy)]
654pub enum ParsePlaylistTypeError {
655    /// The value provided was not `VOD` or `EVENT`.
656    InvalidValue,
657}
658impl Display for ParsePlaylistTypeError {
659    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
660        match self {
661            Self::InvalidValue => write!(f, "expected 'EVENT' or 'VOD'"),
662        }
663    }
664}
665impl Error for ParsePlaylistTypeError {}
666
667/// An error when trying to parse a decimal integer range (`<n>[@<o>]`).
668#[derive(Debug, PartialEq, Clone, Copy)]
669pub enum ParseDecimalIntegerRangeError {
670    /// The length component was not a valid number.
671    InvalidLength(ParseNumberError),
672    /// The offset component was not a valid number.
673    InvalidOffset(ParseNumberError),
674}
675impl Display for ParseDecimalIntegerRangeError {
676    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
677        match self {
678            Self::InvalidLength(e) => write!(f, "invalid length due to {e}"),
679            Self::InvalidOffset(e) => write!(f, "invalid offset due to {e}"),
680        }
681    }
682}
683impl Error for ParseDecimalIntegerRangeError {}
684
685/// An error when trying to parse the `EXT-X-MAP:BYTERANGE` attribute.
686#[derive(Debug, PartialEq, Clone, Copy)]
687pub enum ParseMapByterangeError {
688    /// An error parsing the decimal integer range.
689    RangeParseError(ParseDecimalIntegerRangeError),
690    /// The offset component was missing but it is needed for `MapByterange`.
691    MissingOffset,
692}
693impl Display for ParseMapByterangeError {
694    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
695        match self {
696            Self::RangeParseError(e) => e.fmt(f),
697            Self::MissingOffset => write!(f, "missing offset component"),
698        }
699    }
700}
701impl Error for ParseMapByterangeError {}
702impl From<ParseDecimalIntegerRangeError> for ParseMapByterangeError {
703    fn from(value: ParseDecimalIntegerRangeError) -> Self {
704        Self::RangeParseError(value)
705    }
706}
707
708/// An error found when trying to parse a float from a byte slice (`&[u8]`).
709#[derive(Debug, PartialEq, Clone, Copy)]
710pub struct ParseFloatError;
711impl Display for ParseFloatError {
712    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
713        write!(f, "invalid float")
714    }
715}
716impl Error for ParseFloatError {}
717
718/// An enumerated string provided to a parsed tag was not recognized.
719#[derive(Debug, PartialEq, Clone)]
720pub struct UnrecognizedEnumerationError<'a> {
721    /// The unrecognized value that was found.
722    pub value: &'a str,
723}
724impl<'a> UnrecognizedEnumerationError<'a> {
725    /// Construct a new instance of the error using the unrecognized value that was found.
726    pub fn new(value: &'a str) -> Self {
727        Self { value }
728    }
729}
730impl<'a> Display for UnrecognizedEnumerationError<'a> {
731    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
732        write!(f, "{} is not a recognized enumeration", self.value)
733    }
734}
735impl Error for UnrecognizedEnumerationError<'_> {}
736
737/// An error found when trying to parse a decimal resolution (`<width>x<height>`).
738#[derive(Debug, PartialEq, Clone, Copy)]
739pub enum DecimalResolutionParseError {
740    /// The width component was not a valid integer.
741    InvalidWidth,
742    /// The `x` separator character was missing.
743    MissingSeparator,
744    /// The height component was not a valid integer.
745    InvalidHeight,
746}
747impl Display for DecimalResolutionParseError {
748    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
749        match self {
750            Self::InvalidWidth => write!(f, "not a number for width"),
751            Self::MissingSeparator => write!(f, "missing `x` separator"),
752            Self::InvalidHeight => write!(f, "not a number for height"),
753        }
754    }
755}
756impl Error for DecimalResolutionParseError {}
757
758/// An error found while parsing an attribute list.
759#[derive(Debug, PartialEq, Clone, Copy)]
760pub enum AttributeListParsingError {
761    /// The line ended while reading an attribute name (before we found `=`).
762    EndOfLineWhileReadingAttributeName,
763    /// There was an unexpected character (`"` or `,`) in the attribute name.
764    UnexpectedCharacterInAttributeName,
765    /// The attribute name was empty (no characters between start and `=`).
766    EmptyAttributeName,
767    /// The unquoted attribute value was empty (no characters between start and `,` or end of line).
768    EmptyUnquotedValue,
769    /// Unexpected character in unquoted attribute value (`=` or `"` after initial index).
770    UnexpectedCharacterInAttributeValue,
771    /// Unexpected character occurring after quote end and before `,`.
772    UnexpectedCharacterAfterQuoteEnd,
773    /// The line ended while reading a quoted string value.
774    EndOfLineWhileReadingQuotedValue,
775    /// There was an error when trying to convert to UTF-8.
776    InvalidUtf8(std::str::Utf8Error),
777}
778impl Display for AttributeListParsingError {
779    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
780        match self {
781            Self::EndOfLineWhileReadingAttributeName => {
782                write!(f, "line ended while reading attribute name")
783            }
784            Self::UnexpectedCharacterInAttributeName => {
785                write!(f, "unexpected character in attribute name")
786            }
787            Self::EmptyAttributeName => write!(f, "attribute name with no characters"),
788            Self::EmptyUnquotedValue => write!(f, "unquoted value with no characters"),
789            Self::UnexpectedCharacterInAttributeValue => {
790                write!(f, "unexpected character in attribute value")
791            }
792            Self::UnexpectedCharacterAfterQuoteEnd => {
793                write!(f, "unexpected character between quoted string end and ','")
794            }
795            Self::EndOfLineWhileReadingQuotedValue => {
796                write!(f, "line ended while reading quoted string value")
797            }
798            Self::InvalidUtf8(e) => write!(f, "invalid utf-8 due to {e}"),
799        }
800    }
801}
802impl std::error::Error for AttributeListParsingError {}
803impl From<std::str::Utf8Error> for AttributeListParsingError {
804    fn from(value: std::str::Utf8Error) -> Self {
805        Self::InvalidUtf8(value)
806    }
807}