1use crate::parse::Error;
2use crate::parse::Error::InvalidFragment;
3use std::fmt::{Display, Formatter};
4
5#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
12pub struct Fragment<'a> {
13 fragment: &'a str,
14}
15
16impl Default for Fragment<'static> {
17 fn default() -> Self {
18 Self { fragment: "#" }
19 }
20}
21
22impl<'a> Fragment<'a> {
23 pub unsafe fn new(fragment: &'a str) -> Self {
30 debug_assert!(Self::is_valid(fragment));
31
32 Self { fragment }
33 }
34}
35
36impl<'a> TryFrom<&'a str> for Fragment<'a> {
37 type Error = Error;
38
39 fn try_from(fragment: &'a str) -> Result<Self, Self::Error> {
40 if Self::is_valid(fragment) {
41 Ok(Self { fragment })
42 } else {
43 Err(InvalidFragment)
44 }
45 }
46}
47
48impl<'a> Fragment<'a> {
49 fn is_valid_char(c: u8) -> bool {
53 c.is_ascii_alphanumeric() || c.is_ascii_punctuation()
54 }
55
56 pub fn is_valid(fragment: &str) -> bool {
58 !fragment.is_empty()
59 && fragment.as_bytes()[0] == b'#'
60 && fragment.as_bytes()[1..]
61 .iter()
62 .all(|c| Self::is_valid_char(*c))
63 }
64}
65
66impl<'a> Fragment<'a> {
67 pub fn value(&self) -> &str {
71 &self.fragment[1..]
72 }
73
74 pub const fn as_str(&self) -> &str {
76 self.fragment
77 }
78}
79
80impl<'a> AsRef<str> for Fragment<'a> {
81 fn as_ref(&self) -> &str {
82 self.fragment
83 }
84}
85
86impl<'a> Display for Fragment<'a> {
87 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
88 write!(f, "{}", self.fragment)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use crate::parse::Error::InvalidFragment;
95 use crate::Fragment;
96
97 #[test]
98 fn new() {
99 let fragment: Fragment = unsafe { Fragment::new("#the-fragment") };
100 assert_eq!(fragment.fragment, "#the-fragment");
101 }
102
103 #[test]
104 fn default() {
105 let fragment: Fragment = Fragment::default();
106 assert_eq!(fragment.as_str(), "#");
107 assert_eq!(fragment.value(), "");
108 }
109
110 #[test]
111 fn try_from_str() {
112 assert_eq!(Fragment::try_from("#").unwrap().as_str(), "#");
113 assert_eq!(Fragment::try_from("#section").unwrap().as_str(), "#section");
114 assert_eq!(Fragment::try_from(""), Err(InvalidFragment));
115 assert_eq!(Fragment::try_from("no-hash"), Err(InvalidFragment));
116 assert_eq!(Fragment::try_from("# space"), Err(InvalidFragment));
117 }
118
119 #[test]
120 fn value() {
121 let fragment: Fragment = unsafe { Fragment::new("#the-fragment") };
122 assert_eq!(fragment.value(), "the-fragment");
123
124 let fragment: Fragment = unsafe { Fragment::new("#") };
125 assert_eq!(fragment.value(), "");
126 }
127
128 #[test]
129 fn is_valid() {
130 let test_cases: &[(&str, bool)] = &[
131 ("", false),
132 ("#", true),
133 ("###", true),
134 ("#azAZ09", true),
135 ("#!/&/=/~/", true),
136 ("#?", true),
137 ("#!", true),
138 ("# ", false),
139 ("# x", false),
140 ];
141 for (fragment, expected) in test_cases {
142 let result: bool = Fragment::is_valid(fragment);
143 assert_eq!(result, *expected, "fragment={}", fragment);
144 }
145 }
146
147 #[test]
148 fn display() {
149 let fragment: Fragment = unsafe { Fragment::new("#the-fragment") };
150 assert_eq!(fragment.as_str(), "#the-fragment");
151 assert_eq!(fragment.as_ref(), "#the-fragment");
152 assert_eq!(fragment.to_string(), "#the-fragment");
153 }
154}