xml_string/names/qname.rs
1//! [`QName`].
2//!
3//! [`QName`]: https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-QName
4
5use core::convert::TryFrom;
6use core::fmt;
7use core::num::NonZeroUsize;
8
9use crate::names::error::{NameError, TargetNameType};
10use crate::names::{Eqname, Name, Ncname, Nmtoken};
11
12/// String slice for [`QName`].
13///
14/// [`QName`]: https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-QName
15#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
16#[repr(transparent)]
17pub struct Qname(str);
18
19#[allow(clippy::len_without_is_empty)]
20impl Qname {
21 /// Creates a new `&Qname`.
22 ///
23 /// # Failures
24 ///
25 /// Fails if the given string is not a valid [`QName`].
26 ///
27 /// # Examples
28 ///
29 /// ```
30 /// # use xml_string::names::Qname;
31 /// let noprefix = Qname::from_str("hello")?;
32 /// assert_eq!(noprefix, "hello");
33 ///
34 /// let prefixed = Qname::from_str("foo:bar")?;
35 /// assert_eq!(prefixed, "foo:bar");
36 ///
37 /// assert!(Qname::from_str("").is_err(), "Empty string is not a QName");
38 /// assert!(Qname::from_str("foo bar").is_err(), "Whitespace is not allowed");
39 /// assert!(Qname::from_str("foo:bar:baz").is_err(), "Two or more colons are not allowed");
40 /// assert!(Qname::from_str("0foo").is_err(), "ASCII digit at the beginning is not allowed");
41 /// # Ok::<_, xml_string::names::NameError>(())
42 /// ```
43 ///
44 /// [`QName`]: https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-QName
45 // `FromStr` can be implemented only for types with static lifetime.
46 #[allow(clippy::should_implement_trait)]
47 pub fn from_str(s: &str) -> Result<&Self, NameError> {
48 <&Self>::try_from(s)
49 }
50
51 /// Creates a new `&Qname` without validation.
52 ///
53 /// # Safety
54 ///
55 /// The given string should be a valid [`QName`].
56 ///
57 /// # Examples
58 ///
59 /// ```
60 /// # use xml_string::names::Qname;
61 /// let name = unsafe {
62 /// Qname::new_unchecked("foo:bar")
63 /// };
64 /// assert_eq!(name, "foo:bar");
65 /// ```
66 ///
67 /// [`QName`]: https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-QName
68 #[inline]
69 #[must_use]
70 pub unsafe fn new_unchecked(s: &str) -> &Self {
71 &*(s as *const str as *const Self)
72 }
73
74 /// Validates the given string.
75 fn validate(s: &str) -> Result<(), NameError> {
76 match Self::parse_as_possible(s) {
77 Ok(_) => Ok(()),
78 Err((_colon_pos, valid_up_to)) => {
79 Err(NameError::new(TargetNameType::Qname, valid_up_to))
80 }
81 }
82 }
83
84 /// Parses the given string from the beginning as possible.
85 ///
86 /// Retruns `Ok(colon_position)` if the string is valid QName.
87 /// Returns `Err((colon_position, valid_up_to))` if the string is invalid.
88 pub(super) fn parse_as_possible(
89 s: &str,
90 ) -> Result<Option<NonZeroUsize>, (Option<NonZeroUsize>, usize)> {
91 // Parse the first component (prefix or full QName without prefix).
92 let prefix_len = match Ncname::from_str(s) {
93 Ok(_) => return Ok(None),
94 Err(e) => e.valid_up_to(),
95 };
96 if prefix_len == 0 {
97 // No valid prefix found.
98 return Err((None, 0));
99 }
100 assert!(
101 prefix_len < s.len(),
102 "`prefix_len` cannot be `s.len()`, because `s` is invalid as `Ncname`"
103 );
104 if s.as_bytes()[prefix_len] != b':' {
105 // Prefix does not followed by a colon.
106 return Err((None, prefix_len));
107 }
108 let local_part = &s[(prefix_len + 1)..];
109
110 match Ncname::from_str(local_part) {
111 Ok(_) => Ok(NonZeroUsize::new(prefix_len)),
112 Err(e) if e.valid_up_to() == 0 => Err((None, prefix_len)),
113 Err(e) => Err((
114 NonZeroUsize::new(prefix_len),
115 prefix_len + 1 + e.valid_up_to(),
116 )),
117 }
118 }
119
120 /// Returns the string as `&str`.
121 ///
122 /// # Examples
123 ///
124 /// ```
125 /// # use xml_string::names::Qname;
126 /// let name = Qname::from_str("foo:bar")?;
127 /// assert_eq!(name, "foo:bar");
128 ///
129 /// let s: &str = name.as_str();
130 /// assert_eq!(s, "foo:bar");
131 /// # Ok::<_, xml_string::names::NameError>(())
132 /// ```
133 #[inline]
134 #[must_use]
135 pub fn as_str(&self) -> &str {
136 &self.0
137 }
138
139 /// Returns the length of the string in bytes.
140 ///
141 /// # Examples
142 ///
143 /// ```
144 /// # use xml_string::names::Qname;
145 /// let name = Qname::from_str("foo:bar")?;
146 /// assert_eq!(name.len(), 7);
147 /// # Ok::<_, xml_string::names::NameError>(())
148 /// ```
149 #[inline]
150 #[must_use]
151 pub fn len(&self) -> usize {
152 self.0.len()
153 }
154
155 /// Parses the leading `Qname` and returns the value and the rest input.
156 ///
157 /// # Exmaples
158 ///
159 /// ```
160 /// # use xml_string::names::Qname;
161 /// let input = "hello:012";
162 /// let expected = Qname::from_str("hello").expect("valid Qname");
163 /// assert_eq!(
164 /// Qname::parse_next(input),
165 /// Ok((expected, ":012"))
166 /// );
167 /// # Ok::<_, xml_string::names::NameError>(())
168 /// ```
169 ///
170 /// ```
171 /// # use xml_string::names::Qname;
172 /// let input = "hello:world:foo";
173 /// let expected = Qname::from_str("hello:world").expect("valid Qname");
174 /// assert_eq!(
175 /// Qname::parse_next(input),
176 /// Ok((expected, ":foo"))
177 /// );
178 /// # Ok::<_, xml_string::names::NameError>(())
179 /// ```
180 ///
181 /// ```
182 /// # use xml_string::names::Qname;
183 /// let input = "012";
184 /// assert!(Qname::parse_next(input).is_err());
185 /// # Ok::<_, xml_string::names::NameError>(())
186 /// ```
187 pub fn parse_next(s: &str) -> Result<(&Self, &str), NameError> {
188 match Self::from_str(s) {
189 Ok(v) => Ok((v, &s[s.len()..])),
190 Err(e) if e.valid_up_to() == 0 => Err(e),
191 Err(e) => {
192 let valid_up_to = e.valid_up_to();
193 let v = unsafe {
194 let valid = &s[..valid_up_to];
195 debug_assert!(Self::validate(valid).is_ok());
196 // This is safe because the substring is valid.
197 Self::new_unchecked(valid)
198 };
199 Ok((v, &s[valid_up_to..]))
200 }
201 }
202 }
203
204 /// Returns the length of the prefix, if available.
205 ///
206 /// Note that this is O(length) operation.
207 #[must_use]
208 fn prefix_len(&self) -> Option<NonZeroUsize> {
209 self.as_str().find(':').and_then(NonZeroUsize::new)
210 }
211
212 /// Returns whether the QName has a prefix.
213 ///
214 /// Note that this is O(length) operation.
215 /// Consider using [`ParsedQname::has_prefix`] method if possible.
216 ///
217 /// # Examples
218 ///
219 /// ```
220 /// # use xml_string::names::Qname;
221 /// let local_only = Qname::from_str("hello")?;
222 /// assert!(!local_only.has_prefix());
223 ///
224 /// let prefixed = Qname::from_str("foo:bar")?;
225 /// assert!(prefixed.has_prefix());
226 /// # Ok::<_, xml_string::names::NameError>(())
227 /// ```
228 #[inline]
229 #[must_use]
230 pub fn has_prefix(&self) -> bool {
231 self.as_str().find(':').is_some()
232 }
233
234 /// Returns the prefix if available.
235 ///
236 /// Note that this is O(length) operation.
237 /// Consider using [`ParsedQname::prefix`] method if possible.
238 ///
239 /// # Examples
240 ///
241 /// ```
242 /// # use xml_string::names::Qname;
243 /// let prefixed = Qname::from_str("foo:bar")?;
244 /// assert_eq!(prefixed.prefix().map(|s| s.as_str()), Some("foo"));
245 ///
246 /// let noprefix = Qname::from_str("foo")?;
247 /// assert_eq!(noprefix.prefix().map(|s| s.as_str()), None);
248 /// # Ok::<_, xml_string::names::NameError>(())
249 /// ```
250 #[inline]
251 #[must_use]
252 pub fn prefix(&self) -> Option<&Ncname> {
253 ParsedQname::new(self, self.prefix_len()).prefix()
254 }
255
256 /// Returns the local part.
257 ///
258 /// Note that this is O(length) operation.
259 /// Consider using [`ParsedQname::local_part`] method if possible.
260 ///
261 /// # Examples
262 ///
263 /// ```
264 /// # use xml_string::names::Qname;
265 /// let prefixed = Qname::from_str("foo:bar")?;
266 /// assert_eq!(prefixed.local_part(), "bar");
267 ///
268 /// let noprefix = Qname::from_str("foo")?;
269 /// assert_eq!(noprefix.local_part(), "foo");
270 /// # Ok::<_, xml_string::names::NameError>(())
271 /// ```
272 #[inline]
273 #[must_use]
274 pub fn local_part(&self) -> &Ncname {
275 ParsedQname::new(self, self.prefix_len()).local_part()
276 }
277
278 /// Returns a pair of the prefix (if available) and the local part.
279 ///
280 /// Note that this is O(length) operation.
281 /// Consider using [`ParsedQname::prefix_and_local`] method if possible.
282 ///
283 /// # Examples
284 ///
285 /// ```
286 /// # use xml_string::names::Qname;
287 /// use std::convert::TryFrom;
288 ///
289 /// let noprefix = Qname::from_str("hello")?;
290 /// assert_eq!(noprefix.prefix_and_local(), (noprefix.prefix(), noprefix.local_part()));
291 ///
292 /// let prefixed = Qname::from_str("foo:bar")?;
293 /// assert_eq!(prefixed.prefix_and_local(), (prefixed.prefix(), prefixed.local_part()));
294 /// # Ok::<_, xml_string::names::NameError>(())
295 /// ```
296 #[inline]
297 #[must_use]
298 pub fn prefix_and_local(&self) -> (Option<&Ncname>, &Ncname) {
299 ParsedQname::new(self, self.prefix_len()).prefix_and_local()
300 }
301
302 /// Converts a `Box<Qname>` into a `Box<str>` without copying or allocating.
303 ///
304 /// # Examples
305 ///
306 /// ```
307 /// # use xml_string::names::Qname;
308 /// let name = Qname::from_str("q:name")?;
309 /// let boxed_name: Box<Qname> = name.into();
310 /// assert_eq!(&*boxed_name, name);
311 /// let boxed_str: Box<str> = boxed_name.into_boxed_str();
312 /// assert_eq!(&*boxed_str, name.as_str());
313 /// # Ok::<_, xml_string::names::NameError>(())
314 /// ```
315 #[cfg(feature = "alloc")]
316 pub fn into_boxed_str(self: alloc::boxed::Box<Self>) -> Box<str> {
317 unsafe {
318 // This is safe because `Qname` has the same memory layout as `str`
319 // (thanks to `#[repr(transparent)]`).
320 alloc::boxed::Box::<str>::from_raw(alloc::boxed::Box::<Self>::into_raw(self) as *mut str)
321 }
322 }
323}
324
325impl_traits_for_custom_string_slice!(Qname);
326
327impl AsRef<Eqname> for Qname {
328 #[inline]
329 fn as_ref(&self) -> &Eqname {
330 unsafe {
331 debug_assert!(
332 Eqname::from_str(self.as_str()).is_ok(),
333 "QName {:?} must be a valid Eqname",
334 self.as_str()
335 );
336 // This is safe because a QName is also a valid Eqname.
337 Eqname::new_unchecked(self.as_str())
338 }
339 }
340}
341
342impl AsRef<Name> for Qname {
343 #[inline]
344 fn as_ref(&self) -> &Name {
345 unsafe {
346 debug_assert!(
347 Name::from_str(self.as_str()).is_ok(),
348 "QName {:?} must be a valid Name",
349 self.as_str()
350 );
351 // This is safe because a QName is also a valid Name.
352 Name::new_unchecked(self.as_str())
353 }
354 }
355}
356
357impl AsRef<Nmtoken> for Qname {
358 #[inline]
359 fn as_ref(&self) -> &Nmtoken {
360 unsafe {
361 debug_assert!(
362 Nmtoken::from_str(self.as_str()).is_ok(),
363 "QName {:?} must be a valid Nmtoken",
364 self.as_str()
365 );
366 // This is safe because a QName is also a valid Nmtoken.
367 Nmtoken::new_unchecked(self.as_str())
368 }
369 }
370}
371
372impl<'a> From<&'a Ncname> for &'a Qname {
373 #[inline]
374 fn from(s: &'a Ncname) -> Self {
375 s.as_ref()
376 }
377}
378
379impl<'a> From<ParsedQname<'a>> for &'a Qname {
380 #[inline]
381 fn from(s: ParsedQname<'a>) -> Self {
382 s.content
383 }
384}
385
386impl<'a> TryFrom<&'a str> for &'a Qname {
387 type Error = NameError;
388
389 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
390 Qname::validate(s)?;
391 Ok(unsafe {
392 // This is safe because the string is validated.
393 Qname::new_unchecked(s)
394 })
395 }
396}
397
398impl<'a> TryFrom<&'a Qname> for &'a Ncname {
399 type Error = NameError;
400
401 fn try_from(s: &'a Qname) -> Result<Self, Self::Error> {
402 if let Some(p_len) = s.prefix_len() {
403 return Err(NameError::new(TargetNameType::Ncname, p_len.get()));
404 }
405
406 unsafe {
407 debug_assert!(
408 Ncname::from_str(s.as_str()).is_ok(),
409 "QName {:?} without prefix must be a valid NCName",
410 s.as_str()
411 );
412 // This is safe because a QName without prefix is also a valid NCName.
413 Ok(Ncname::new_unchecked(s.as_str()))
414 }
415 }
416}
417
418/// Parsed [`QName`] reference.
419///
420/// [`QName`]: https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-QName
421#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
422pub struct ParsedQname<'a> {
423 /// Content string.
424 content: &'a Qname,
425 /// Length of the prefix, if available.
426 ///
427 /// If this is `Some(p_len)`, `self.content.as_str().as_bytes()[p_len] == b':'` is guaranteed.
428 prefix_len: Option<NonZeroUsize>,
429}
430
431#[allow(clippy::len_without_is_empty)]
432impl<'a> ParsedQname<'a> {
433 /// Creates a new `ParsedQname`.
434 ///
435 /// # Panics
436 ///
437 /// Panics if the `prefix_len` does not point to the colon in the `content`.
438 #[must_use]
439 pub(super) fn new(content: &'a Qname, prefix_len: Option<NonZeroUsize>) -> Self {
440 if let Some(p_len) = prefix_len {
441 if content.as_str().as_bytes()[p_len.get()] != b':' {
442 panic!(
443 "`prefix_len` (={:?}) should point to the colon \
444 (if available) of the qname {:?}",
445 p_len.get(),
446 content
447 );
448 }
449 }
450 Self {
451 content,
452 prefix_len,
453 }
454 }
455
456 /// Creates a new `ParsedQname<'_>` from the given string slice.
457 ///
458 /// # Failures
459 ///
460 /// Fails if the given string is not a valid [`QName`].
461 ///
462 /// # Examples
463 ///
464 /// ```
465 /// # use xml_string::names::ParsedQname;
466 /// let noprefix = ParsedQname::from_str("hello")?;
467 /// assert_eq!(noprefix, "hello");
468 ///
469 /// let prefixed = ParsedQname::from_str("foo:bar")?;
470 /// assert_eq!(prefixed, "foo:bar");
471 ///
472 /// assert!(ParsedQname::from_str("").is_err(), "Empty string is not a QName");
473 /// assert!(ParsedQname::from_str("foo bar").is_err(), "Whitespace is not allowed");
474 /// assert!(ParsedQname::from_str("foo:bar:baz").is_err(), "Two or more colons are not allowed");
475 /// assert!(ParsedQname::from_str("0foo").is_err(), "ASCII digit at the beginning is not allowed");
476 /// # Ok::<_, xml_string::names::NameError>(())
477 /// ```
478 ///
479 /// [`QName`]: https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-QName
480 // `FromStr` can be implemented only for types with static lifetime.
481 #[allow(clippy::should_implement_trait)]
482 #[inline]
483 pub fn from_str(s: &'a str) -> Result<Self, NameError> {
484 Self::try_from(s)
485 }
486
487 /// Returns the string as `&Qname`.
488 ///
489 /// # Exmaples
490 ///
491 /// ```
492 /// # use xml_string::names::ParsedQname;
493 /// use xml_string::names::Qname;
494 ///
495 /// let name = ParsedQname::from_str("hello")?;
496 /// assert_eq!(name, "hello");
497 ///
498 /// let s: &Qname = name.as_qname();
499 /// assert_eq!(s, "hello");
500 /// # Ok::<_, xml_string::names::NameError>(())
501 /// ```
502 #[inline]
503 #[must_use]
504 pub fn as_qname(&self) -> &'a Qname {
505 self.content
506 }
507
508 /// Returns the string as `&str`.
509 ///
510 /// # Exmaples
511 ///
512 /// ```
513 /// # use xml_string::names::ParsedQname;
514 /// let name = ParsedQname::from_str("hello")?;
515 /// assert_eq!(name, "hello");
516 ///
517 /// let s: &str = name.as_str();
518 /// assert_eq!(s, "hello");
519 /// # Ok::<_, xml_string::names::NameError>(())
520 /// ```
521 #[inline]
522 #[must_use]
523 pub fn as_str(&self) -> &'a str {
524 self.content.as_str()
525 }
526
527 /// Returns the length of the string in bytes.
528 ///
529 /// # Examples
530 ///
531 /// ```
532 /// # use xml_string::names::ParsedQname;
533 /// let name = ParsedQname::from_str("foo:bar")?;
534 /// assert_eq!(name.len(), 7);
535 /// # Ok::<_, xml_string::names::NameError>(())
536 /// ```
537 #[inline]
538 #[must_use]
539 pub fn len(&self) -> usize {
540 self.content.len()
541 }
542
543 /// Returns whether the QName has a prefix.
544 ///
545 /// # Examples
546 ///
547 /// ```
548 /// # use xml_string::names::ParsedQname;
549 /// let local_only = ParsedQname::from_str("hello")?;
550 /// assert!(!local_only.has_prefix());
551 ///
552 /// let prefixed = ParsedQname::from_str("foo:bar")?;
553 /// assert!(prefixed.has_prefix());
554 /// # Ok::<_, xml_string::names::NameError>(())
555 /// ```
556 #[inline]
557 #[must_use]
558 pub fn has_prefix(&self) -> bool {
559 self.prefix_len.is_some()
560 }
561
562 /// Returns the prefix, if available.
563 ///
564 /// # Examples
565 ///
566 /// ```
567 /// # use xml_string::names::ParsedQname;
568 /// let prefixed = ParsedQname::from_str("foo:bar")?;
569 /// assert_eq!(prefixed.prefix().map(|s| s.as_str()), Some("foo"));
570 ///
571 /// let noprefix = ParsedQname::from_str("foo")?;
572 /// assert_eq!(noprefix.prefix().map(|s| s.as_str()), None);
573 /// # Ok::<_, xml_string::names::NameError>(())
574 /// ```
575 #[must_use]
576 pub fn prefix(&self) -> Option<&'a Ncname> {
577 self.prefix_len.as_ref().map(|p_len| {
578 let prefix = &self.as_str()[..p_len.get()];
579 unsafe {
580 debug_assert!(
581 Ncname::from_str(prefix).is_ok(),
582 "The prefix {:?} must be a valid NCName",
583 prefix
584 );
585 // This is safe because the prefix is a valid NCName.
586 Ncname::new_unchecked(prefix)
587 }
588 })
589 }
590
591 /// Returns the local part.
592 ///
593 /// # Examples
594 ///
595 /// ```
596 /// # use xml_string::names::ParsedQname;
597 /// let prefixed = ParsedQname::from_str("foo:bar")?;
598 /// assert_eq!(prefixed.local_part(), "bar");
599 ///
600 /// let noprefix = ParsedQname::from_str("foo")?;
601 /// assert_eq!(noprefix.local_part(), "foo");
602 /// # Ok::<_, xml_string::names::NameError>(())
603 /// ```
604 #[must_use]
605 pub fn local_part(&self) -> &'a Ncname {
606 let start = self.prefix_len.as_ref().map_or(0, |p_len| p_len.get() + 1);
607 let local_part = &self.as_str()[start..];
608 unsafe {
609 debug_assert!(
610 Ncname::from_str(local_part).is_ok(),
611 "The local part {:?} must be a valid NCName",
612 local_part
613 );
614 // This is safe because the local part is a valid NCName.
615 Ncname::new_unchecked(local_part)
616 }
617 }
618
619 /// Returns a pair of the prefix (if available) and the local part.
620 ///
621 /// This is efficient version of `(self.prefix(), self.local_part())`.
622 ///
623 /// # Examples
624 ///
625 /// ```
626 /// # use xml_string::names::ParsedQname;
627 /// use std::convert::TryFrom;
628 ///
629 /// let noprefix = ParsedQname::from_str("hello")?;
630 /// assert_eq!(noprefix.prefix_and_local(), (noprefix.prefix(), noprefix.local_part()));
631 ///
632 /// let prefixed = ParsedQname::from_str("foo:bar")?;
633 /// assert_eq!(prefixed.prefix_and_local(), (prefixed.prefix(), prefixed.local_part()));
634 /// # Ok::<_, xml_string::names::NameError>(())
635 /// ```
636 #[must_use]
637 pub fn prefix_and_local(&self) -> (Option<&'a Ncname>, &'a Ncname) {
638 match self.prefix_len {
639 Some(p_len) => {
640 let prefix = {
641 let prefix = &self.as_str()[..p_len.get()];
642 unsafe {
643 debug_assert!(
644 Ncname::from_str(prefix).is_ok(),
645 "The prefix {:?} must be a valid NCName",
646 prefix
647 );
648 // This is safe because the prefix is a valid NCName.
649 Ncname::new_unchecked(prefix)
650 }
651 };
652 let local_part = {
653 let local_part = &self.as_str()[(p_len.get() + 1)..];
654 unsafe {
655 debug_assert!(
656 Ncname::from_str(local_part).is_ok(),
657 "The local part {:?} must be a valid NCName",
658 local_part
659 );
660 // This is safe because the local part is a valid NCName.
661 Ncname::new_unchecked(local_part)
662 }
663 };
664 (Some(prefix), local_part)
665 }
666 None => {
667 let ncname = unsafe {
668 debug_assert!(
669 Ncname::from_str(self.as_str()).is_ok(),
670 "QName without prefix must be a valid NCName"
671 );
672 Ncname::new_unchecked(self.as_str())
673 };
674 (None, ncname)
675 }
676 }
677 }
678}
679
680impl PartialEq<str> for ParsedQname<'_> {
681 #[inline]
682 fn eq(&self, other: &str) -> bool {
683 self.as_str() == other
684 }
685}
686impl_cmp!(str, ParsedQname<'_>);
687
688impl PartialEq<&'_ str> for ParsedQname<'_> {
689 #[inline]
690 fn eq(&self, other: &&str) -> bool {
691 self.as_str() == *other
692 }
693}
694impl_cmp!(&str, ParsedQname<'_>);
695
696impl PartialEq<str> for &'_ ParsedQname<'_> {
697 #[inline]
698 fn eq(&self, other: &str) -> bool {
699 self.as_str() == other
700 }
701}
702impl_cmp!(str, &ParsedQname<'_>);
703
704#[cfg(feature = "alloc")]
705impl PartialEq<alloc::string::String> for ParsedQname<'_> {
706 #[inline]
707 fn eq(&self, other: &alloc::string::String) -> bool {
708 self.as_str() == *other
709 }
710}
711#[cfg(feature = "alloc")]
712impl_cmp!(alloc::string::String, ParsedQname<'_>);
713
714#[cfg(feature = "alloc")]
715impl PartialEq<&alloc::string::String> for ParsedQname<'_> {
716 #[inline]
717 fn eq(&self, other: &&alloc::string::String) -> bool {
718 self.as_str() == **other
719 }
720}
721#[cfg(feature = "alloc")]
722impl_cmp!(&alloc::string::String, ParsedQname<'_>);
723
724#[cfg(feature = "alloc")]
725impl PartialEq<alloc::boxed::Box<str>> for ParsedQname<'_> {
726 #[inline]
727 fn eq(&self, other: &alloc::boxed::Box<str>) -> bool {
728 self.as_str() == other.as_ref()
729 }
730}
731#[cfg(feature = "alloc")]
732impl_cmp!(alloc::boxed::Box<str>, ParsedQname<'_>);
733
734#[cfg(feature = "alloc")]
735impl PartialEq<alloc::borrow::Cow<'_, str>> for ParsedQname<'_> {
736 #[inline]
737 fn eq(&self, other: &alloc::borrow::Cow<'_, str>) -> bool {
738 self.as_str() == *other
739 }
740}
741#[cfg(feature = "alloc")]
742impl_cmp!(alloc::borrow::Cow<'_, str>, ParsedQname<'_>);
743
744impl AsRef<str> for ParsedQname<'_> {
745 #[inline]
746 fn as_ref(&self) -> &str {
747 self.as_str()
748 }
749}
750
751impl AsRef<Qname> for ParsedQname<'_> {
752 #[inline]
753 fn as_ref(&self) -> &Qname {
754 self.content
755 }
756}
757
758impl AsRef<Eqname> for ParsedQname<'_> {
759 #[inline]
760 fn as_ref(&self) -> &Eqname {
761 self.content.as_ref()
762 }
763}
764
765impl<'a> From<&'a Qname> for ParsedQname<'a> {
766 fn from(s: &'a Qname) -> Self {
767 let prefix_len = s.as_str().find(':').and_then(NonZeroUsize::new);
768 Self {
769 content: s,
770 prefix_len,
771 }
772 }
773}
774
775impl<'a> TryFrom<&'a str> for ParsedQname<'a> {
776 type Error = NameError;
777
778 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
779 match Qname::parse_as_possible(s) {
780 Ok(prefix_len) => {
781 let content = unsafe {
782 // This is safe because the string is validated by
783 // `Qname::parse_as_possible()`.
784 Qname::new_unchecked(s)
785 };
786 Ok(Self {
787 content,
788 prefix_len,
789 })
790 }
791 Err((_colon_pos, valid_up_to)) => {
792 Err(NameError::new(TargetNameType::Qname, valid_up_to))
793 }
794 }
795 }
796}
797
798impl fmt::Debug for ParsedQname<'_> {
799 #[inline]
800 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
801 f.write_str(self.as_str())
802 }
803}
804
805impl fmt::Display for ParsedQname<'_> {
806 #[inline]
807 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
808 f.write_str(self.as_str())
809 }
810}
811
812#[cfg(test)]
813mod tests {
814 use super::*;
815
816 fn ncname(s: &str) -> &Ncname {
817 Ncname::from_str(s)
818 .unwrap_or_else(|e| panic!("Failed to create Ncname from {:?}: {}", s, e))
819 }
820
821 fn qname(s: &str) -> &Qname {
822 Qname::from_str(s).unwrap_or_else(|e| panic!("Failed to create Qname from {:?}: {}", s, e))
823 }
824
825 fn parsed_qname(s: &str) -> ParsedQname<'_> {
826 ParsedQname::from_str(s)
827 .unwrap_or_else(|e| panic!("Failed to create ParsedQname from {:?}: {}", s, e))
828 }
829
830 fn ensure_eq(s: &str) {
831 assert_eq!(
832 Qname::from_str(s).expect("Should not fail"),
833 s,
834 "String: {:?}",
835 s
836 );
837 }
838
839 fn ensure_error_at(s: &str, valid_up_to: usize) {
840 let err = Qname::from_str(s).expect_err("Should fail");
841 assert_eq!(err.valid_up_to(), valid_up_to, "String: {:?}", s);
842 }
843
844 #[test]
845 fn qname_str_valid() {
846 ensure_eq("hello");
847 ensure_eq("abc123");
848 ensure_eq("foo:bar");
849 }
850
851 #[test]
852 fn qname_str_invalid() {
853 ensure_error_at("", 0);
854 ensure_error_at("-foo", 0);
855 ensure_error_at("0foo", 0);
856 ensure_error_at("foo bar", 3);
857 ensure_error_at("foo/bar", 3);
858
859 ensure_error_at("foo:bar:baz", 7);
860 ensure_error_at(":foo", 0);
861 ensure_error_at("foo:", 3);
862 ensure_error_at("foo::bar", 3);
863 ensure_error_at("foo:-bar", 3);
864 }
865
866 #[test]
867 fn parse_as_possible() {
868 assert_eq!(Qname::parse_as_possible("foo"), Ok(None));
869 assert_eq!(
870 Qname::parse_as_possible("foo:bar"),
871 Ok(NonZeroUsize::new(3))
872 );
873
874 assert_eq!(Qname::parse_as_possible(""), Err((None, 0)));
875 assert_eq!(Qname::parse_as_possible("foo:"), Err((None, 3)));
876 assert_eq!(Qname::parse_as_possible(":foo"), Err((None, 0)));
877 assert_eq!(
878 Qname::parse_as_possible("foo:bar:baz"),
879 Err((NonZeroUsize::new(3), 7))
880 );
881 assert_eq!(Qname::parse_as_possible("foo::bar"), Err((None, 3)));
882 }
883
884 #[test]
885 fn parsed_qname_from_str() {
886 assert_eq!(
887 ParsedQname::from_str("hello").map(|v| v.as_qname()),
888 Ok(qname("hello"))
889 );
890 assert_eq!(
891 ParsedQname::from_str("foo:bar").map(|v| v.as_qname()),
892 Ok(qname("foo:bar"))
893 );
894
895 assert_eq!(
896 ParsedQname::from_str("foo:-bar"),
897 Err(NameError::new(TargetNameType::Qname, 3))
898 );
899 }
900
901 #[test]
902 fn parsed_qname_prefix() {
903 assert_eq!(parsed_qname("hello").prefix(), None);
904 assert_eq!(parsed_qname("foo:bar").prefix(), Some(ncname("foo")));
905 }
906
907 #[test]
908 fn parsed_qname_local_part() {
909 assert_eq!(parsed_qname("hello").local_part(), ncname("hello"));
910 assert_eq!(parsed_qname("foo:bar").local_part(), ncname("bar"));
911 }
912
913 #[test]
914 fn parsed_qname_prefix_and_local() {
915 assert_eq!(
916 parsed_qname("hello").prefix_and_local(),
917 (None, ncname("hello"))
918 );
919 assert_eq!(
920 parsed_qname("foo:bar").prefix_and_local(),
921 (Some(ncname("foo")), ncname("bar"))
922 );
923 }
924}