Skip to content

Commit

Permalink
Merge pull request #95 from Kiwifuit/master
Browse files Browse the repository at this point in the history
An Error Type for `Selector::parse`
  • Loading branch information
cfvescovo committed Dec 19, 2022
2 parents 24d2e4d + 78b4d53 commit 3fba089
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 5 deletions.
121 changes: 121 additions & 0 deletions src/error.rs
@@ -0,0 +1,121 @@
//! Custom error types for diagnostics
//! Includes re-exported error types from dependencies

mod utils;

use std::{error::Error, fmt::Display};

use cssparser::{BasicParseErrorKind, ParseErrorKind, Token};
use selectors::parser::SelectorParseErrorKind;

/// Error type that is returned when calling `Selector::parse`
#[derive(Debug, Clone)]
pub enum SelectorErrorKind<'a> {
/// A `Token` was not expected
UnexpectedToken(Token<'a>),

/// End-Of-Line was unexpected
EndOfLine,

/// `@` rule is invalid
InvalidAtRule(String),

/// The body of an `@` rule is invalid
InvalidAtRuleBody,

/// The qualified rule is invalid
QualRuleInvalid,

/// Expected a `::` for a pseudoelement
ExpectedColonOnPseudoElement(Token<'a>),

/// Expected an identity for a pseudoelement
ExpectedIdentityOnPseudoElement(Token<'a>),

/// A `SelectorParseErrorKind` error that isn't really supposed to happen did
UnexpectedSelectorParseError(SelectorParseErrorKind<'a>),
}

impl<'a> From<cssparser::ParseError<'a, SelectorParseErrorKind<'a>>> for SelectorErrorKind<'a> {
fn from(original: cssparser::ParseError<'a, SelectorParseErrorKind<'a>>) -> Self {
// NOTE: This could be improved, but I dont
// exactly know how
match original.kind {
ParseErrorKind::Basic(err) => SelectorErrorKind::from(err),
ParseErrorKind::Custom(err) => SelectorErrorKind::from(err),
}
}
}

impl<'a> From<BasicParseErrorKind<'a>> for SelectorErrorKind<'a> {
fn from(err: BasicParseErrorKind<'a>) -> Self {
match err {
BasicParseErrorKind::UnexpectedToken(token) => Self::UnexpectedToken(token),
BasicParseErrorKind::EndOfInput => Self::EndOfLine,
BasicParseErrorKind::AtRuleInvalid(rule) => {
Self::InvalidAtRule(rule.clone().to_string())
}
BasicParseErrorKind::AtRuleBodyInvalid => Self::InvalidAtRuleBody,
BasicParseErrorKind::QualifiedRuleInvalid => Self::QualRuleInvalid,
}
}
}

impl<'a> From<SelectorParseErrorKind<'a>> for SelectorErrorKind<'a> {
fn from(err: SelectorParseErrorKind<'a>) -> Self {
match err {
SelectorParseErrorKind::PseudoElementExpectedColon(token) => {
Self::ExpectedColonOnPseudoElement(token)
}
SelectorParseErrorKind::PseudoElementExpectedIdent(token) => {
Self::ExpectedIdentityOnPseudoElement(token)
}
other => Self::UnexpectedSelectorParseError(other),
}
}
}

impl<'a> Display for SelectorErrorKind<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::UnexpectedToken(token) => {
format!("Token {:?} was not expected", utils::render_token(token))
}
Self::EndOfLine => "Unexpected EOL".to_string(),
Self::InvalidAtRule(rule) => format!("Invalid @-rule {:?}", rule),
Self::InvalidAtRuleBody => "The body of an @-rule was invalid".to_string(),
Self::QualRuleInvalid => "The qualified name was invalid".to_string(),
Self::ExpectedColonOnPseudoElement(token) => format!(
"Expected a ':' token for pseudoelement, got {:?} instead",
utils::render_token(token)
),
Self::ExpectedIdentityOnPseudoElement(token) => format!(
"Expected identity for pseudoelement, got {:?} instead",
utils::render_token(token)
),
Self::UnexpectedSelectorParseError(err) => format!(
"Unexpected error occurred. Please report this to the developer\n{:#?}",
err
),
}
)
}
}

impl<'a> Error for SelectorErrorKind<'a> {
fn description(&self) -> &str {
match self {
Self::UnexpectedToken(_) => "Token was not expected",
Self::EndOfLine => "Unexpected EOL",
Self::InvalidAtRule(_) => "Invalid @-rule",
Self::InvalidAtRuleBody => "The body of an @-rule was invalid",
Self::QualRuleInvalid => "The qualified name was invalid",
Self::ExpectedColonOnPseudoElement(_) => "Missing colon character on pseudoelement",
Self::ExpectedIdentityOnPseudoElement(_) => "Missing pseudoelement identity",
Self::UnexpectedSelectorParseError(_) => "Unexpected error",
}
}
}
91 changes: 91 additions & 0 deletions src/error/utils.rs
@@ -0,0 +1,91 @@
use cssparser::Token;

pub(crate) fn render_token(token: &Token<'_>) -> String {
// THIS TOOK FOREVER TO IMPLEMENT

match token {
// TODO: Give these guys some better names
Token::Ident(ident) => format!("{}", ident.clone()),
Token::AtKeyword(value) => format!("@{}", value.clone()),
Token::Hash(name) | Token::IDHash(name) => format!("#{}", name.clone()),
Token::QuotedString(value) => format!("\"{}\"", value.clone()),
Token::Number {
has_sign: signed,
value: num,
int_value: _,
}
| Token::Percentage {
has_sign: signed,
unit_value: num,
int_value: _,
} => render_number(*signed, *num, token),
Token::Dimension {
has_sign: signed,
value: num,
int_value: _,
unit,
} => format!("{}{}", render_int(*signed, *num), unit),
Token::WhiteSpace(_) => String::from(" "),
Token::Comment(comment) => format!("/* {} */", comment),
Token::Function(name) => format!("{}()", name.clone()),
Token::BadString(string) => format!("<Bad String {:?}>", string.clone()),
Token::BadUrl(url) => format!("<Bad URL {:?}>", url.clone()),
// Single-character token
sc_token => render_single_char_token(sc_token),
}
}

fn render_single_char_token(token: &Token) -> String {
String::from(match token {
Token::Colon => ":",
Token::Semicolon => ";",
Token::Comma => ",",
Token::IncludeMatch => "~=",
Token::DashMatch => "|=",
Token::PrefixMatch => "^=",
Token::SuffixMatch => "$=",
Token::SubstringMatch => "*=",
Token::CDO => "<!--",
Token::CDC => "-->",
Token::ParenthesisBlock => "<(",
Token::SquareBracketBlock => "<[",
Token::CurlyBracketBlock => "<{",
Token::CloseParenthesis => "<)",
Token::CloseSquareBracket => "<]",
Token::CloseCurlyBracket => "<}",
other => panic!(
"Token {:?} is not supposed to match as a single-character token!",
other
),
})
}

fn render_number(signed: bool, num: f32, token: &Token) -> String {
let num = render_int(signed, num);

match token {
Token::Number { .. } => num,
Token::Percentage { .. } => format!("{}%", num),
_ => panic!("render_number is not supposed to be called on a non-numerical token"),
}
}

fn render_int(signed: bool, num: f32) -> String {
if signed {
render_int_signed(num)
} else {
render_int_unsigned(num)
}
}

fn render_int_signed(num: f32) -> String {
if num > 0.0 {
format!("+{}", num)
} else {
format!("-{}", num)
}
}

fn render_int_unsigned(num: f32) -> String {
format!("{}", num)
}
1 change: 1 addition & 0 deletions src/lib.rs
Expand Up @@ -146,6 +146,7 @@ pub use crate::node::Node;
pub use crate::selector::Selector;

pub mod element_ref;
pub mod error;
pub mod html;
pub mod node;
pub mod selector;
Expand Down
12 changes: 7 additions & 5 deletions src/selector.rs
Expand Up @@ -9,6 +9,7 @@ use html5ever::{LocalName, Namespace};
use selectors::parser::SelectorParseErrorKind;
use selectors::{matching, parser, visitor};

use crate::error::SelectorErrorKind;
use crate::ElementRef;

/// Wrapper around CSS selectors.
Expand All @@ -23,12 +24,13 @@ pub struct Selector {
impl Selector {
/// Parses a CSS selector group.

pub fn parse(
selectors: &'_ str,
) -> Result<Self, cssparser::ParseError<'_, SelectorParseErrorKind<'_>>> {
pub fn parse(selectors: &'_ str) -> Result<Self, SelectorErrorKind> {
let mut parser_input = cssparser::ParserInput::new(selectors);
let mut parser = cssparser::Parser::new(&mut parser_input);
parser::SelectorList::parse(&Parser, &mut parser).map(|list| Selector { selectors: list.0 })

parser::SelectorList::parse(&Parser, &mut parser)
.map(|list| Selector { selectors: list.0 })
.map_err(SelectorErrorKind::from)
}

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

impl<'i> TryFrom<&'i str> for Selector {
type Error = cssparser::ParseError<'i, SelectorParseErrorKind<'i>>;
type Error = SelectorErrorKind<'i>;

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

0 comments on commit 3fba089

Please sign in to comment.