Skip to main content

m3u8_parser/m3u8/
error.rs

1//! Error types returned when reading M3U8 playlists.
2
3use std::fmt;
4use std::io;
5
6/// An error encountered while reading a playlist.
7#[derive(Debug)]
8#[non_exhaustive]
9pub enum ParseError {
10    /// The playlist could not be read from its source.
11    Io(io::Error),
12    /// A line of the playlist is malformed.
13    Syntax {
14        /// The 1-based line number where the error was found.
15        line: usize,
16        kind: SyntaxError,
17    },
18}
19
20impl ParseError {
21    /// Returns the 1-based line number for syntax errors.
22    pub fn line(&self) -> Option<usize> {
23        match self {
24            ParseError::Syntax { line, .. } => Some(*line),
25            ParseError::Io(_) => None,
26        }
27    }
28
29    /// Returns the kind of syntax error, if this is a syntax error.
30    pub fn syntax(&self) -> Option<&SyntaxError> {
31        match self {
32            ParseError::Syntax { kind, .. } => Some(kind),
33            ParseError::Io(_) => None,
34        }
35    }
36}
37
38impl fmt::Display for ParseError {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        match self {
41            ParseError::Io(error) => write!(f, "failed to read playlist: {error}"),
42            ParseError::Syntax { line, kind } => write!(f, "line {line}: {kind}"),
43        }
44    }
45}
46
47impl std::error::Error for ParseError {
48    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
49        match self {
50            ParseError::Io(error) => Some(error),
51            ParseError::Syntax { kind, .. } => Some(kind),
52        }
53    }
54}
55
56impl From<io::Error> for ParseError {
57    fn from(error: io::Error) -> Self {
58        ParseError::Io(error)
59    }
60}
61
62/// An error encountered while substituting EXT-X-DEFINE variables.
63#[derive(Debug)]
64#[non_exhaustive]
65pub enum SubstitutionError {
66    /// A `{$name}` reference has no definition.
67    UndefinedVariable(String),
68    /// An `IMPORT` names a variable missing from the imported variables.
69    MissingImport(String),
70    /// A `QUERYPARAM` names a parameter missing from the playlist URI query.
71    MissingQueryParameter(String),
72    /// A variable is defined more than once.
73    DuplicateVariable(String),
74    /// An EXT-X-DEFINE tag does not have exactly one of `NAME`/`VALUE`,
75    /// `IMPORT`, or `QUERYPARAM`, or its name is invalid.
76    InvalidDefine(String),
77    /// A variable's value contains a control character or a double quote, which
78    /// would change the structure of the playlist once substituted.
79    InvalidValue(String),
80    /// The playlist could not be parsed after substitution.
81    Parse(ParseError),
82}
83
84impl fmt::Display for SubstitutionError {
85    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86        match self {
87            SubstitutionError::UndefinedVariable(name) => write!(f, "undefined variable: {name}"),
88            SubstitutionError::MissingImport(name) => {
89                write!(f, "missing imported variable: {name}")
90            }
91            SubstitutionError::MissingQueryParameter(name) => {
92                write!(f, "missing query parameter: {name}")
93            }
94            SubstitutionError::DuplicateVariable(name) => {
95                write!(f, "variable defined more than once: {name}")
96            }
97            SubstitutionError::InvalidDefine(value) => write!(f, "invalid EXT-X-DEFINE: {value}"),
98            SubstitutionError::InvalidValue(name) => {
99                write!(
100                    f,
101                    "variable value contains a control character or quote: {name}"
102                )
103            }
104            SubstitutionError::Parse(error) => write!(f, "{error}"),
105        }
106    }
107}
108
109impl std::error::Error for SubstitutionError {
110    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
111        match self {
112            SubstitutionError::Parse(error) => Some(error),
113            _ => None,
114        }
115    }
116}
117
118/// The reason a playlist line could not be parsed.
119#[derive(Debug, Clone, PartialEq)]
120#[non_exhaustive]
121pub enum SyntaxError {
122    /// An `#EXTINF` tag was not followed by a media segment URI.
123    MissingUriAfterExtInf,
124    /// An `#EXTINF` tag is malformed.
125    InvalidExtInf(String),
126    /// An attribute list could not be split into `NAME=VALUE` pairs.
127    MalformedAttributeList { reason: &'static str, input: String },
128    /// A required attribute is absent.
129    MissingAttribute {
130        tag: &'static str,
131        attribute: &'static str,
132    },
133    /// An attribute value does not have the type the tag requires.
134    InvalidAttributeValue {
135        tag: &'static str,
136        attribute: &'static str,
137        value: String,
138    },
139    /// A tag that requires a value has none.
140    MissingTagValue { tag: &'static str },
141    /// A tag's value does not have the type the tag requires.
142    InvalidTagValue { tag: &'static str, value: String },
143    /// A tag's attributes are present in a combination the specification forbids.
144    ConflictingAttributes {
145        tag: &'static str,
146        reason: &'static str,
147    },
148}
149
150impl fmt::Display for SyntaxError {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        match self {
153            SyntaxError::MissingUriAfterExtInf => write!(f, "missing URI after EXTINF"),
154            SyntaxError::InvalidExtInf(value) => write!(f, "invalid EXTINF: {value}"),
155            SyntaxError::MalformedAttributeList { reason, input } => {
156                write!(f, "malformed attribute list ({reason}): {input}")
157            }
158            SyntaxError::MissingAttribute { tag, attribute } => {
159                write!(f, "{tag} requires {attribute}")
160            }
161            SyntaxError::InvalidAttributeValue {
162                tag,
163                attribute,
164                value,
165            } => write!(f, "invalid {tag} {attribute}: {value}"),
166            SyntaxError::MissingTagValue { tag } => write!(f, "{tag} requires a value"),
167            SyntaxError::InvalidTagValue { tag, value } => write!(f, "invalid {tag}: {value}"),
168            SyntaxError::ConflictingAttributes { tag, reason } => write!(f, "{tag}: {reason}"),
169        }
170    }
171}
172
173impl std::error::Error for SyntaxError {}