Skip to main content

takumi_css/
error.rs

1use cssparser::{BasicParseErrorKind, ParseError, ParseErrorKind};
2use selectors::parser::SelectorParseErrorKind;
3use std::borrow::Cow;
4use thiserror::Error;
5
6use crate::keyframes::KeyframePreludeParseError;
7
8/// Errors raised while parsing a CSS declaration block string.
9#[derive(Error, Debug, Clone, PartialEq, Eq)]
10pub enum StyleDeclarationBlockParseError {
11  /// The declaration block could not be parsed as CSS declarations.
12  #[error("failed to parse CSS declaration block `{input}` near `{context}`: {reason}")]
13  InvalidDeclarationBlock {
14    /// The original declaration block input.
15    input: String,
16    /// The declaration slice being parsed when the error was raised.
17    context: String,
18    /// The parser failure rendered as text.
19    reason: String,
20  },
21}
22
23/// Errors raised while parsing a CSS stylesheet string.
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct StyleSheetParseError {
26  /// The stylesheet slice being parsed when the error was raised.
27  pub context: Option<String>,
28  /// The specific stylesheet parse failure.
29  pub kind: StyleSheetParseErrorKind,
30}
31
32/// The specific stylesheet parse failure.
33#[derive(Error, Debug, Clone, PartialEq, Eq)]
34pub enum StyleSheetParseErrorKind {
35  /// The stylesheet could not be parsed as valid CSS.
36  #[error("{0}")]
37  InvalidStyleSheet(String),
38
39  /// The stylesheet uses an unsupported media feature.
40  #[error("unsupported media feature")]
41  UnsupportedMediaFeature,
42
43  /// `@property` requires `inherits` to be `true` or `false`.
44  #[error("@property inherits must be true or false")]
45  PropertyInheritsMustBeBoolean,
46
47  /// `@property` is missing its `syntax` descriptor.
48  #[error("missing `@property` syntax")]
49  MissingPropertySyntax,
50
51  /// `@property` is missing its `inherits` descriptor.
52  #[error("missing `@property` inherits")]
53  MissingPropertyInherits,
54
55  /// `@supports` mixed `and` and `or` without parentheses.
56  #[error("@supports cannot mix `and` and `or` without parentheses")]
57  SupportsMixedAndOrWithoutParentheses,
58
59  /// `@property` names must be custom properties.
60  #[error("@property name must be a custom property")]
61  PropertyNameMustBeCustomProperty,
62
63  /// `@layer` blocks accept at most one name.
64  #[error("@layer blocks accept at most one name")]
65  LayerBlockMultipleNames,
66
67  /// Nested `@keyframes` and `@property` rules are not supported.
68  #[error("unsupported nested at-rule")]
69  UnsupportedNestedAtRule,
70}
71
72impl std::fmt::Display for StyleSheetParseError {
73  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74    if let Some(context) = &self.context {
75      write!(
76        f,
77        "failed to parse stylesheet near `{context}`: {}",
78        self.kind
79      )
80    } else {
81      write!(f, "failed to parse stylesheet: {}", self.kind)
82    }
83  }
84}
85
86impl std::error::Error for StyleSheetParseError {}
87
88impl<'i> From<SelectorParseErrorKind<'i>> for StyleSheetParseError {
89  fn from(err: SelectorParseErrorKind<'i>) -> Self {
90    Self::invalid_reason(format!("{err:?}"))
91  }
92}
93
94impl<'i> From<Cow<'i, str>> for StyleSheetParseError {
95  fn from(err: Cow<'i, str>) -> Self {
96    Self::invalid_reason(err.into_owned())
97  }
98}
99
100impl<'i> From<KeyframePreludeParseError<'i>> for StyleSheetParseError {
101  fn from(_err: KeyframePreludeParseError<'i>) -> Self {
102    Self::invalid_reason(format!("{:?}", BasicParseErrorKind::QualifiedRuleInvalid))
103  }
104}
105
106impl StyleSheetParseError {
107  pub fn invalid_reason(reason: impl Into<String>) -> Self {
108    Self::new(StyleSheetParseErrorKind::InvalidStyleSheet(reason.into()))
109  }
110
111  pub fn unsupported_media_feature() -> Self {
112    Self::new(StyleSheetParseErrorKind::UnsupportedMediaFeature)
113  }
114
115  pub fn property_inherits_must_be_boolean() -> Self {
116    Self::new(StyleSheetParseErrorKind::PropertyInheritsMustBeBoolean)
117  }
118
119  pub fn missing_property_syntax() -> Self {
120    Self::new(StyleSheetParseErrorKind::MissingPropertySyntax)
121  }
122
123  pub fn missing_property_inherits() -> Self {
124    Self::new(StyleSheetParseErrorKind::MissingPropertyInherits)
125  }
126
127  pub fn supports_mixed_and_or_without_parentheses() -> Self {
128    Self::new(StyleSheetParseErrorKind::SupportsMixedAndOrWithoutParentheses)
129  }
130
131  pub fn property_name_must_be_custom_property() -> Self {
132    Self::new(StyleSheetParseErrorKind::PropertyNameMustBeCustomProperty)
133  }
134
135  pub fn layer_block_multiple_names() -> Self {
136    Self::new(StyleSheetParseErrorKind::LayerBlockMultipleNames)
137  }
138
139  pub fn unsupported_nested_at_rule() -> Self {
140    Self::new(StyleSheetParseErrorKind::UnsupportedNestedAtRule)
141  }
142
143  fn new(kind: StyleSheetParseErrorKind) -> Self {
144    Self {
145      context: None,
146      kind,
147    }
148  }
149
150  fn with_context(self, context: &str) -> Self {
151    Self {
152      context: Some(context.to_owned()),
153      kind: self.kind,
154    }
155  }
156
157  pub fn from_parse_error(context: &str, error: ParseError<'_, StyleSheetParseError>) -> Self {
158    match error.kind {
159      ParseErrorKind::Basic(error) => Self::invalid_reason(format!("{error:?}")),
160      ParseErrorKind::Custom(error) => error,
161    }
162    .with_context(context)
163  }
164}