Skip to content

Commit 3fba089

Browse files
authored
Merge pull request #95 from Kiwifuit/master
An Error Type for `Selector::parse`
2 parents 24d2e4d + 78b4d53 commit 3fba089

File tree

4 files changed

+220
-5
lines changed

4 files changed

+220
-5
lines changed

src/error.rs

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//! Custom error types for diagnostics
2+
//! Includes re-exported error types from dependencies
3+
4+
mod utils;
5+
6+
use std::{error::Error, fmt::Display};
7+
8+
use cssparser::{BasicParseErrorKind, ParseErrorKind, Token};
9+
use selectors::parser::SelectorParseErrorKind;
10+
11+
/// Error type that is returned when calling `Selector::parse`
12+
#[derive(Debug, Clone)]
13+
pub enum SelectorErrorKind<'a> {
14+
/// A `Token` was not expected
15+
UnexpectedToken(Token<'a>),
16+
17+
/// End-Of-Line was unexpected
18+
EndOfLine,
19+
20+
/// `@` rule is invalid
21+
InvalidAtRule(String),
22+
23+
/// The body of an `@` rule is invalid
24+
InvalidAtRuleBody,
25+
26+
/// The qualified rule is invalid
27+
QualRuleInvalid,
28+
29+
/// Expected a `::` for a pseudoelement
30+
ExpectedColonOnPseudoElement(Token<'a>),
31+
32+
/// Expected an identity for a pseudoelement
33+
ExpectedIdentityOnPseudoElement(Token<'a>),
34+
35+
/// A `SelectorParseErrorKind` error that isn't really supposed to happen did
36+
UnexpectedSelectorParseError(SelectorParseErrorKind<'a>),
37+
}
38+
39+
impl<'a> From<cssparser::ParseError<'a, SelectorParseErrorKind<'a>>> for SelectorErrorKind<'a> {
40+
fn from(original: cssparser::ParseError<'a, SelectorParseErrorKind<'a>>) -> Self {
41+
// NOTE: This could be improved, but I dont
42+
// exactly know how
43+
match original.kind {
44+
ParseErrorKind::Basic(err) => SelectorErrorKind::from(err),
45+
ParseErrorKind::Custom(err) => SelectorErrorKind::from(err),
46+
}
47+
}
48+
}
49+
50+
impl<'a> From<BasicParseErrorKind<'a>> for SelectorErrorKind<'a> {
51+
fn from(err: BasicParseErrorKind<'a>) -> Self {
52+
match err {
53+
BasicParseErrorKind::UnexpectedToken(token) => Self::UnexpectedToken(token),
54+
BasicParseErrorKind::EndOfInput => Self::EndOfLine,
55+
BasicParseErrorKind::AtRuleInvalid(rule) => {
56+
Self::InvalidAtRule(rule.clone().to_string())
57+
}
58+
BasicParseErrorKind::AtRuleBodyInvalid => Self::InvalidAtRuleBody,
59+
BasicParseErrorKind::QualifiedRuleInvalid => Self::QualRuleInvalid,
60+
}
61+
}
62+
}
63+
64+
impl<'a> From<SelectorParseErrorKind<'a>> for SelectorErrorKind<'a> {
65+
fn from(err: SelectorParseErrorKind<'a>) -> Self {
66+
match err {
67+
SelectorParseErrorKind::PseudoElementExpectedColon(token) => {
68+
Self::ExpectedColonOnPseudoElement(token)
69+
}
70+
SelectorParseErrorKind::PseudoElementExpectedIdent(token) => {
71+
Self::ExpectedIdentityOnPseudoElement(token)
72+
}
73+
other => Self::UnexpectedSelectorParseError(other),
74+
}
75+
}
76+
}
77+
78+
impl<'a> Display for SelectorErrorKind<'a> {
79+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80+
write!(
81+
f,
82+
"{}",
83+
match self {
84+
Self::UnexpectedToken(token) => {
85+
format!("Token {:?} was not expected", utils::render_token(token))
86+
}
87+
Self::EndOfLine => "Unexpected EOL".to_string(),
88+
Self::InvalidAtRule(rule) => format!("Invalid @-rule {:?}", rule),
89+
Self::InvalidAtRuleBody => "The body of an @-rule was invalid".to_string(),
90+
Self::QualRuleInvalid => "The qualified name was invalid".to_string(),
91+
Self::ExpectedColonOnPseudoElement(token) => format!(
92+
"Expected a ':' token for pseudoelement, got {:?} instead",
93+
utils::render_token(token)
94+
),
95+
Self::ExpectedIdentityOnPseudoElement(token) => format!(
96+
"Expected identity for pseudoelement, got {:?} instead",
97+
utils::render_token(token)
98+
),
99+
Self::UnexpectedSelectorParseError(err) => format!(
100+
"Unexpected error occurred. Please report this to the developer\n{:#?}",
101+
err
102+
),
103+
}
104+
)
105+
}
106+
}
107+
108+
impl<'a> Error for SelectorErrorKind<'a> {
109+
fn description(&self) -> &str {
110+
match self {
111+
Self::UnexpectedToken(_) => "Token was not expected",
112+
Self::EndOfLine => "Unexpected EOL",
113+
Self::InvalidAtRule(_) => "Invalid @-rule",
114+
Self::InvalidAtRuleBody => "The body of an @-rule was invalid",
115+
Self::QualRuleInvalid => "The qualified name was invalid",
116+
Self::ExpectedColonOnPseudoElement(_) => "Missing colon character on pseudoelement",
117+
Self::ExpectedIdentityOnPseudoElement(_) => "Missing pseudoelement identity",
118+
Self::UnexpectedSelectorParseError(_) => "Unexpected error",
119+
}
120+
}
121+
}

src/error/utils.rs

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use cssparser::Token;
2+
3+
pub(crate) fn render_token(token: &Token<'_>) -> String {
4+
// THIS TOOK FOREVER TO IMPLEMENT
5+
6+
match token {
7+
// TODO: Give these guys some better names
8+
Token::Ident(ident) => format!("{}", ident.clone()),
9+
Token::AtKeyword(value) => format!("@{}", value.clone()),
10+
Token::Hash(name) | Token::IDHash(name) => format!("#{}", name.clone()),
11+
Token::QuotedString(value) => format!("\"{}\"", value.clone()),
12+
Token::Number {
13+
has_sign: signed,
14+
value: num,
15+
int_value: _,
16+
}
17+
| Token::Percentage {
18+
has_sign: signed,
19+
unit_value: num,
20+
int_value: _,
21+
} => render_number(*signed, *num, token),
22+
Token::Dimension {
23+
has_sign: signed,
24+
value: num,
25+
int_value: _,
26+
unit,
27+
} => format!("{}{}", render_int(*signed, *num), unit),
28+
Token::WhiteSpace(_) => String::from(" "),
29+
Token::Comment(comment) => format!("/* {} */", comment),
30+
Token::Function(name) => format!("{}()", name.clone()),
31+
Token::BadString(string) => format!("<Bad String {:?}>", string.clone()),
32+
Token::BadUrl(url) => format!("<Bad URL {:?}>", url.clone()),
33+
// Single-character token
34+
sc_token => render_single_char_token(sc_token),
35+
}
36+
}
37+
38+
fn render_single_char_token(token: &Token) -> String {
39+
String::from(match token {
40+
Token::Colon => ":",
41+
Token::Semicolon => ";",
42+
Token::Comma => ",",
43+
Token::IncludeMatch => "~=",
44+
Token::DashMatch => "|=",
45+
Token::PrefixMatch => "^=",
46+
Token::SuffixMatch => "$=",
47+
Token::SubstringMatch => "*=",
48+
Token::CDO => "<!--",
49+
Token::CDC => "-->",
50+
Token::ParenthesisBlock => "<(",
51+
Token::SquareBracketBlock => "<[",
52+
Token::CurlyBracketBlock => "<{",
53+
Token::CloseParenthesis => "<)",
54+
Token::CloseSquareBracket => "<]",
55+
Token::CloseCurlyBracket => "<}",
56+
other => panic!(
57+
"Token {:?} is not supposed to match as a single-character token!",
58+
other
59+
),
60+
})
61+
}
62+
63+
fn render_number(signed: bool, num: f32, token: &Token) -> String {
64+
let num = render_int(signed, num);
65+
66+
match token {
67+
Token::Number { .. } => num,
68+
Token::Percentage { .. } => format!("{}%", num),
69+
_ => panic!("render_number is not supposed to be called on a non-numerical token"),
70+
}
71+
}
72+
73+
fn render_int(signed: bool, num: f32) -> String {
74+
if signed {
75+
render_int_signed(num)
76+
} else {
77+
render_int_unsigned(num)
78+
}
79+
}
80+
81+
fn render_int_signed(num: f32) -> String {
82+
if num > 0.0 {
83+
format!("+{}", num)
84+
} else {
85+
format!("-{}", num)
86+
}
87+
}
88+
89+
fn render_int_unsigned(num: f32) -> String {
90+
format!("{}", num)
91+
}

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ pub use crate::node::Node;
146146
pub use crate::selector::Selector;
147147

148148
pub mod element_ref;
149+
pub mod error;
149150
pub mod html;
150151
pub mod node;
151152
pub mod selector;

src/selector.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use html5ever::{LocalName, Namespace};
99
use selectors::parser::SelectorParseErrorKind;
1010
use selectors::{matching, parser, visitor};
1111

12+
use crate::error::SelectorErrorKind;
1213
use crate::ElementRef;
1314

1415
/// Wrapper around CSS selectors.
@@ -23,12 +24,13 @@ pub struct Selector {
2324
impl Selector {
2425
/// Parses a CSS selector group.
2526
26-
pub fn parse(
27-
selectors: &'_ str,
28-
) -> Result<Self, cssparser::ParseError<'_, SelectorParseErrorKind<'_>>> {
27+
pub fn parse(selectors: &'_ str) -> Result<Self, SelectorErrorKind> {
2928
let mut parser_input = cssparser::ParserInput::new(selectors);
3029
let mut parser = cssparser::Parser::new(&mut parser_input);
31-
parser::SelectorList::parse(&Parser, &mut parser).map(|list| Selector { selectors: list.0 })
30+
31+
parser::SelectorList::parse(&Parser, &mut parser)
32+
.map(|list| Selector { selectors: list.0 })
33+
.map_err(SelectorErrorKind::from)
3234
}
3335

3436
/// Returns true if the element matches this selector.
@@ -140,7 +142,7 @@ impl cssparser::ToCss for PseudoElement {
140142
}
141143

142144
impl<'i> TryFrom<&'i str> for Selector {
143-
type Error = cssparser::ParseError<'i, SelectorParseErrorKind<'i>>;
145+
type Error = SelectorErrorKind<'i>;
144146

145147
fn try_from(s: &'i str) -> Result<Self, Self::Error> {
146148
Selector::parse(s)

0 commit comments

Comments
 (0)