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}