vue_sfc/ast/block/attribute/
name.rs

1use std::{
2    borrow::{Borrow, Cow},
3    fmt::Display,
4    ops::Deref,
5};
6
7pub use self::error::InvalidAttributeName;
8
9mod error {
10    use std::error::Error;
11    use std::fmt::Display;
12
13    /// Returned when a function was unable to convert a string to an
14    /// [`AttributeName`][super::AttributeName].
15    #[derive(Debug)]
16    pub struct InvalidAttributeName(pub(super) char);
17
18    impl Display for InvalidAttributeName {
19        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20            write!(f, "illegal char: `{}`", self.0)
21        }
22    }
23
24    impl Error for InvalidAttributeName {}
25}
26
27/// The name of an attribute, i.e: `lang` in `<script lang="ts">`.
28#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
29#[must_use]
30pub struct AttributeName<'a>(Cow<'a, str>);
31
32impl<'a> AttributeName<'a> {
33    /// Attempts to convert a string to an [`AttributeName`].
34    ///
35    /// # Errors
36    /// Will return an error if the string contains any of the following characters:
37    /// - `U+0009 CHARACTER TABULATION`
38    /// - `U+000A LINE FEED`
39    /// - `U+000C FORM FEED`
40    /// - `U+0020 SPACE`
41    /// - `U+002F SOLIDUS (/)`
42    /// - `U+003D EQUAL SIGN (=)`
43    /// - `U+003E GREATER-THAN SIGN (>)`.
44    pub fn from_cow(mut src: Cow<'a, str>) -> Result<Self, InvalidAttributeName> {
45        if let Some(ch) = src.chars().find(|ch| {
46            matches!(
47                ch,
48                '\u{0009}'
49                    | '\u{000A}'
50                    | '\u{000C}'
51                    | '\u{0020}'
52                    | '\u{002F}'
53                    | '\u{003D}'
54                    | '\u{003E}'
55            )
56        }) {
57            return Err(InvalidAttributeName(ch));
58        }
59
60        if src.contains(|ch: char| ch.is_ascii_uppercase()) {
61            src.to_mut().make_ascii_lowercase();
62
63            Ok(Self(src))
64        } else {
65            Ok(Self(src))
66        }
67    }
68
69    /// Convert a string into an [`AttributeName`] **without** validating
70    /// (unless `debug_assertions` is enabled).
71    ///
72    /// # Panics
73    /// If `debug_assertions` is enabled, validate the input and panic on failure.
74    ///
75    /// # Safety
76    /// See string prerequisites of [`AttributeName::from_cow`].
77    pub unsafe fn from_cow_unchecked(src: Cow<'a, str>) -> Self {
78        if cfg!(debug_assertions) {
79            match Self::from_cow(src) {
80                Ok(val) => val,
81                Err(err) => {
82                    panic!("AttributeName::from_cow_unchecked(): {err}")
83                }
84            }
85        } else {
86            Self(src)
87        }
88    }
89
90    #[must_use]
91    pub fn as_str(&self) -> &str {
92        &self.0
93    }
94}
95
96impl Deref for AttributeName<'_> {
97    type Target = str;
98
99    fn deref(&self) -> &Self::Target {
100        self.as_str()
101    }
102}
103
104impl Borrow<str> for AttributeName<'_> {
105    fn borrow(&self) -> &str {
106        self.as_str()
107    }
108}
109
110impl Display for AttributeName<'_> {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        self.as_str().fmt(f)
113    }
114}
115
116impl<'a> TryFrom<&'a str> for AttributeName<'a> {
117    type Error = InvalidAttributeName;
118    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
119        Self::from_cow(Cow::Borrowed(value))
120    }
121}
122
123impl<'a> TryFrom<String> for AttributeName<'a> {
124    type Error = InvalidAttributeName;
125    fn try_from(value: String) -> Result<Self, Self::Error> {
126        Self::from_cow(Cow::Owned(value))
127    }
128}
129
130impl<'a> TryFrom<Cow<'a, str>> for AttributeName<'a> {
131    type Error = InvalidAttributeName;
132    fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {
133        Self::from_cow(value)
134    }
135}