// Copyright (c) 2017, 2018 lumi <lumi@pew.im>
// Copyright (c) 2017, 2018, 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
// Copyright (c) 2017, 2018, 2019 Maxime “pep” Buquet <pep@bouah.net>
// Copyright (c) 2017, 2018 Astro <astro@spaceboyz.net>
// Copyright (c) 2017 Bastien Orivel <eijebong@bananium.fr>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#![no_std]
#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, doc(auto_cfg))]

//! Represents XMPP addresses, also known as JabberIDs (JIDs) for the [XMPP](https://xmpp.org/)
//! protocol. A [`Jid`] can have between one and three parts in the form `node@domain/resource`:
//! - the (optional) node part designates a specific account/service on a server, for example
//!   `username@server.com`
//! - the domain part designates a server, for example `irc.jabberfr.org`
//! - the (optional) resource part designates a more specific client, such as a participant in a
//!   groupchat (`jabberfr@chat.jabberfr.org/user`) or a specific client device associated with an
//!   account (`user@example.com/dino`)
//!
//! The [`Jid`] enum can be one of two variants, containing a more specific type:
//! - [`BareJid`] (`Jid::Bare` variant): a JID without a resource
//! - [`FullJid`] (`Jid::Full` variant): a JID with a resource
//!
//! Jids as per the XMPP protocol only ever contain valid UTF-8. However, creating any form of Jid
//! can fail in one of the following cases:
//! - wrong syntax: creating a Jid with an empty (yet declared) node or resource part, such as
//!   `@example.com` or `user@example.com/`
//! - stringprep error: some characters were invalid according to the stringprep algorithm, such as
//!   mixing left-to-write and right-to-left characters

extern crate alloc;

#[cfg(test)]
extern crate std;

use alloc::borrow::Cow;
use alloc::format;
use alloc::string::{String, ToString};
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::mem;
use core::net::{Ipv4Addr, Ipv6Addr};
use core::num::NonZeroU16;
use core::ops::Deref;
use core::str::FromStr;

use memchr::memchr2_iter;

use idna::uts46::{AsciiDenyList, DnsLength, Hyphens, Uts46};
use stringprep::{nameprep, nodeprep, resourceprep};

#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};

#[cfg(feature = "quote")]
use proc_macro2::TokenStream;
#[cfg(feature = "quote")]
use quote::{ToTokens, quote};

#[cfg(feature = "minidom")]
use minidom::{IntoAttributeValue, Node};

mod error;
mod parts;

pub use crate::error::Error;
pub use parts::{DomainPart, DomainRef, NodePart, NodeRef, ResourcePart, ResourceRef};

fn length_check(len: usize, error_empty: Error, error_too_long: Error) -> Result<(), Error> {
    if len == 0 {
        Err(error_empty)
    } else if len > 1023 {
        Err(error_too_long)
    } else {
        Ok(())
    }
}

fn node_check(node: &str) -> Result<Cow<'_, str>, Error> {
    let node = nodeprep(node).map_err(|_| Error::NodePrep)?;
    length_check(node.len(), Error::NodeEmpty, Error::NodeTooLong)?;
    Ok(node)
}

fn resource_check(resource: &str) -> Result<Cow<'_, str>, Error> {
    let resource = resourceprep(resource).map_err(|_| Error::ResourcePrep)?;
    length_check(resource.len(), Error::ResourceEmpty, Error::ResourceTooLong)?;
    Ok(resource)
}

fn domain_check(mut domain: &str) -> Result<Cow<'_, str>, Error> {
    // First, check if this is an IPv4 address.
    if Ipv4Addr::from_str(domain).is_ok() {
        return Ok(Cow::Borrowed(domain));
    }

    // Then if this is an IPv6 address.
    if domain.starts_with('[')
        && domain.ends_with(']')
        && Ipv6Addr::from_str(&domain[1..domain.len() - 1]).is_ok()
    {
        return Ok(Cow::Borrowed(domain));
    }

    // idna can handle the root dot for us, but we still want to remove it for normalization
    // purposes.
    if domain.ends_with('.') {
        domain = &domain[..domain.len() - 1];
    }

    Uts46::new()
        .to_ascii(
            domain.as_bytes(),
            AsciiDenyList::URL,
            Hyphens::Check,
            DnsLength::Verify,
        )
        .map_err(|_| Error::Idna)?;
    let domain = nameprep(domain).map_err(|_| Error::NamePrep)?;
    Ok(domain)
}

/// A struct representing a Jabber ID (JID).
///
/// This JID can either be "bare" (without a `/resource` suffix) or full (with
/// a resource suffix).
///
/// In many APIs, it is appropriate to use the more specific types
/// ([`BareJid`] or [`FullJid`]) instead, as these two JID types are generally
/// used in different contexts within XMPP.
///
/// This dynamic type on the other hand can be used in contexts where it is
/// not known, at compile-time, whether a JID is full or bare.
#[derive(Clone, Eq)]
pub struct Jid {
    normalized: String,
    at: Option<NonZeroU16>,
    slash: Option<NonZeroU16>,
}

impl PartialEq for Jid {
    fn eq(&self, other: &Jid) -> bool {
        self.normalized == other.normalized
    }
}

impl PartialOrd for Jid {
    fn partial_cmp(&self, other: &Jid) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Jid {
    fn cmp(&self, other: &Jid) -> Ordering {
        self.normalized.cmp(&other.normalized)
    }
}

impl Hash for Jid {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.normalized.hash(state)
    }
}

impl FromStr for Jid {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::new(s)
    }
}

impl From<BareJid> for Jid {
    fn from(other: BareJid) -> Self {
        other.inner
    }
}

impl From<FullJid> for Jid {
    fn from(other: FullJid) -> Self {
        other.inner
    }
}

impl fmt::Display for Jid {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.write_str(&self.normalized)
    }
}

impl Jid {
    /// Constructs a Jabber ID from a string. This is of the form
    /// `node`@`domain`/`resource`, where node and resource parts are optional.
    /// If you want a non-fallible version, use [`Jid::from_parts`] instead.
    ///
    /// # Examples
    ///
    /// ```
    /// use jid::Jid;
    /// # use jid::Error;
    ///
    /// # fn main() -> Result<(), Error> {
    /// let jid = Jid::new("node@domain/resource")?;
    ///
    /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
    /// assert_eq!(jid.domain().as_str(), "domain");
    /// assert_eq!(jid.resource().map(|x| x.as_str()), Some("resource"));
    /// # Ok(())
    /// # }
    /// ```
    pub fn new(unnormalized: &str) -> Result<Jid, Error> {
        let bytes = unnormalized.as_bytes();
        let orig_at;
        let orig_slash;
        let mut iter = memchr2_iter(b'@', b'/', bytes);
        let normalized = if let Some(first_index) = iter.next() {
            let byte = bytes[first_index];
            if byte == b'@' {
                if let Some(second_index) = iter.next() {
                    let byte = bytes[second_index];
                    if byte == b'/' {
                        let node = node_check(&unnormalized[..first_index])?;
                        let domain = domain_check(&unnormalized[first_index + 1..second_index])?;
                        let resource = resource_check(&unnormalized[second_index + 1..])?;

                        orig_at = Some(node.len());
                        orig_slash = Some(node.len() + domain.len() + 1);
                        match (node, domain, resource) {
                            (Cow::Borrowed(_), Cow::Borrowed(_), Cow::Borrowed(_)) => {
                                unnormalized.to_string()
                            }
                            (node, domain, resource) => format!("{node}@{domain}/{resource}"),
                        }
                    } else
                    /* This is another '@' character. */
                    {
                        return Err(Error::TooManyAts);
                    }
                } else {
                    // That is a node@domain JID.

                    let node = node_check(&unnormalized[..first_index])?;
                    let domain = domain_check(&unnormalized[first_index + 1..])?;

                    orig_at = Some(node.len());
                    orig_slash = None;
                    match (node, domain) {
                        (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
                        (node, domain) => format!("{node}@{domain}"),
                    }
                }
            } else
            /* This is a '/' character. */
            {
                // The JID is of the form domain/resource, we can stop looking for further
                // characters.

                let domain = domain_check(&unnormalized[..first_index])?;
                let resource = resource_check(&unnormalized[first_index + 1..])?;

                orig_at = None;
                orig_slash = Some(domain.len());
                match (domain, resource) {
                    (Cow::Borrowed(_), Cow::Borrowed(_)) => unnormalized.to_string(),
                    (domain, resource) => format!("{domain}/{resource}"),
                }
            }
        } else {
            // Last possible case, just a domain JID.
            let domain = domain_check(unnormalized)?;

            orig_at = None;
            orig_slash = None;
            domain.into_owned()
        };

        Ok(Self {
            normalized,
            at: orig_at.and_then(|x| NonZeroU16::new(x as u16)),
            slash: orig_slash.and_then(|x| NonZeroU16::new(x as u16)),
        })
    }

    /// Returns the inner String of this JID.
    pub fn into_inner(self) -> String {
        self.normalized
    }

    /// Build a [`Jid`] from typed parts. This method cannot fail because it uses parts that have
    /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
    ///
    /// This method allocates and does not consume the typed parts. To avoid
    /// allocation if both `node` and `resource` are known to be `None` and
    /// `domain` is owned, you can use `domain.into()`.
    pub fn from_parts(
        node: Option<&NodeRef>,
        domain: &DomainRef,
        resource: Option<&ResourceRef>,
    ) -> Self {
        match resource {
            Some(resource) => FullJid::from_parts(node, domain, resource).into(),
            None => BareJid::from_parts(node, domain).into(),
        }
    }

    /// The optional node part of the JID as reference.
    pub fn node(&self) -> Option<&NodeRef> {
        self.at.map(|at| {
            let at = u16::from(at) as usize;
            NodeRef::from_str_unchecked(&self.normalized[..at])
        })
    }

    /// The domain part of the JID as reference
    pub fn domain(&self) -> &DomainRef {
        match (self.at, self.slash) {
            (Some(at), Some(slash)) => {
                let at = u16::from(at) as usize;
                let slash = u16::from(slash) as usize;
                DomainRef::from_str_unchecked(&self.normalized[at + 1..slash])
            }
            (Some(at), None) => {
                let at = u16::from(at) as usize;
                DomainRef::from_str_unchecked(&self.normalized[at + 1..])
            }
            (None, Some(slash)) => {
                let slash = u16::from(slash) as usize;
                DomainRef::from_str_unchecked(&self.normalized[..slash])
            }
            (None, None) => DomainRef::from_str_unchecked(&self.normalized),
        }
    }

    /// The optional resource of the Jabber ID. It is guaranteed to be present when the JID is
    /// a Full variant, which you can check with [`Jid::is_full`].
    pub fn resource(&self) -> Option<&ResourceRef> {
        self.slash.map(|slash| {
            let slash = u16::from(slash) as usize;
            ResourceRef::from_str_unchecked(&self.normalized[slash + 1..])
        })
    }

    /// Allocate a new [`BareJid`] from this JID, discarding the resource.
    pub fn to_bare(&self) -> BareJid {
        BareJid::from_parts(self.node(), self.domain())
    }

    /// Transforms this JID into a [`BareJid`], throwing away the resource.
    ///
    /// ```
    /// # use jid::{BareJid, Jid};
    /// let jid: Jid = "foo@bar/baz".parse().unwrap();
    /// let bare = jid.into_bare();
    /// assert_eq!(bare.to_string(), "foo@bar");
    /// ```
    pub fn into_bare(mut self) -> BareJid {
        if let Some(slash) = self.slash {
            // truncate the string
            self.normalized.truncate(slash.get() as usize);
            self.slash = None;
        }
        BareJid { inner: self }
    }

    /// Checks if the JID is a full JID.
    pub fn is_full(&self) -> bool {
        self.slash.is_some()
    }

    /// Checks if the JID is a bare JID.
    pub fn is_bare(&self) -> bool {
        self.slash.is_none()
    }

    /// Return a reference to the canonical string representation of the JID.
    pub fn as_str(&self) -> &str {
        &self.normalized
    }

    /// Try to convert this Jid to a [`FullJid`] if it contains a resource
    /// and return a [`BareJid`] otherwise.
    ///
    /// This is useful for match blocks:
    ///
    /// ```
    /// # use jid::Jid;
    /// let jid: Jid = "foo@bar".parse().unwrap();
    /// match jid.try_into_full() {
    ///     Ok(full) => println!("it is full: {:?}", full),
    ///     Err(bare) => println!("it is bare: {:?}", bare),
    /// }
    /// ```
    pub fn try_into_full(self) -> Result<FullJid, BareJid> {
        if self.slash.is_some() {
            Ok(FullJid { inner: self })
        } else {
            Err(BareJid { inner: self })
        }
    }

    /// Try to convert this Jid reference to a [`&FullJid`][`FullJid`] if it
    /// contains a resource and return a [`&BareJid`][`BareJid`] otherwise.
    ///
    /// This is useful for match blocks:
    ///
    /// ```
    /// # use jid::Jid;
    /// let jid: Jid = "foo@bar".parse().unwrap();
    /// match jid.try_as_full() {
    ///     Ok(full) => println!("it is full: {:?}", full),
    ///     Err(bare) => println!("it is bare: {:?}", bare),
    /// }
    /// ```
    pub fn try_as_full(&self) -> Result<&FullJid, &BareJid> {
        if self.slash.is_some() {
            Ok(unsafe {
                // SAFETY: FullJid is #[repr(transparent)] of Jid
                // SOUNDNESS: we asserted that self.slash is set above
                mem::transmute::<&Jid, &FullJid>(self)
            })
        } else {
            Err(unsafe {
                // SAFETY: BareJid is #[repr(transparent)] of Jid
                // SOUNDNESS: we asserted that self.slash is unset above
                mem::transmute::<&Jid, &BareJid>(self)
            })
        }
    }

    /// Try to convert this mutable Jid reference to a
    /// [`&mut FullJid`][`FullJid`] if it contains a resource and return a
    /// [`&mut BareJid`][`BareJid`] otherwise.
    pub fn try_as_full_mut(&mut self) -> Result<&mut FullJid, &mut BareJid> {
        if self.slash.is_some() {
            Ok(unsafe {
                // SAFETY: FullJid is #[repr(transparent)] of Jid
                // SOUNDNESS: we asserted that self.slash is set above
                mem::transmute::<&mut Jid, &mut FullJid>(self)
            })
        } else {
            Err(unsafe {
                // SAFETY: BareJid is #[repr(transparent)] of Jid
                // SOUNDNESS: we asserted that self.slash is unset above
                mem::transmute::<&mut Jid, &mut BareJid>(self)
            })
        }
    }

    #[doc(hidden)]
    #[allow(non_snake_case)]
    #[deprecated(
        since = "0.11.0",
        note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
    )]
    pub fn Bare(other: BareJid) -> Self {
        Self::from(other)
    }

    #[doc(hidden)]
    #[allow(non_snake_case)]
    #[deprecated(
        since = "0.11.0",
        note = "use Jid::from (for construction of Jid values) or Jid::try_into_full/Jid::try_as_full (for match blocks) instead"
    )]
    pub fn Full(other: FullJid) -> Self {
        Self::from(other)
    }
}

impl TryFrom<Jid> for FullJid {
    type Error = Error;

    fn try_from(inner: Jid) -> Result<Self, Self::Error> {
        if inner.slash.is_none() {
            return Err(Error::ResourceMissingInFullJid);
        }
        Ok(Self { inner })
    }
}

impl TryFrom<Jid> for BareJid {
    type Error = Error;

    fn try_from(inner: Jid) -> Result<Self, Self::Error> {
        if inner.slash.is_some() {
            return Err(Error::ResourceInBareJid);
        }
        Ok(Self { inner })
    }
}

impl PartialEq<Jid> for FullJid {
    fn eq(&self, other: &Jid) -> bool {
        &self.inner == other
    }
}

impl PartialEq<Jid> for BareJid {
    fn eq(&self, other: &Jid) -> bool {
        &self.inner == other
    }
}

impl PartialEq<FullJid> for Jid {
    fn eq(&self, other: &FullJid) -> bool {
        self == &other.inner
    }
}

impl PartialEq<BareJid> for Jid {
    fn eq(&self, other: &BareJid) -> bool {
        self == &other.inner
    }
}

/// A struct representing a full Jabber ID, with a resource part.
///
/// A full JID is composed of 3 components, of which only the node is optional:
///
/// - the (optional) node part is the part before the (optional) `@`.
/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
/// - the resource part after the `/`.
///
/// Unlike a [`BareJid`], it always contains a resource, and should only be used when you are
/// certain there is no case where a resource can be missing.  Otherwise, use a [`Jid`] or
/// [`BareJid`].
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)] // WARNING: Jid::try_as_* relies on this for safety!
pub struct FullJid {
    inner: Jid,
}

/// A struct representing a bare Jabber ID, without a resource part.
///
/// A bare JID is composed of 2 components, of which only the node is optional:
/// - the (optional) node part is the part before the (optional) `@`.
/// - the domain part is the mandatory part between the (optional) `@` and before the `/`.
///
/// Unlike a [`FullJid`], it can’t contain a resource, and should only be used when you are certain
/// there is no case where a resource can be set.  Otherwise, use a [`Jid`] or [`FullJid`].
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)] // WARNING: Jid::try_as_* relies on this for safety!
pub struct BareJid {
    inner: Jid,
}

impl Deref for FullJid {
    type Target = Jid;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl Deref for BareJid {
    type Target = Jid;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl Borrow<Jid> for FullJid {
    fn borrow(&self) -> &Jid {
        &self.inner
    }
}

impl Borrow<Jid> for BareJid {
    fn borrow(&self) -> &Jid {
        &self.inner
    }
}

impl fmt::Debug for Jid {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_tuple("Jid").field(&self.normalized).finish()
    }
}

impl fmt::Debug for FullJid {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_tuple("FullJid")
            .field(&self.inner.normalized)
            .finish()
    }
}

impl fmt::Debug for BareJid {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.debug_tuple("BareJid")
            .field(&self.inner.normalized)
            .finish()
    }
}

impl fmt::Display for FullJid {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.inner, fmt)
    }
}

impl fmt::Display for BareJid {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.inner, fmt)
    }
}

#[cfg(feature = "serde")]
impl Serialize for Jid {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(&self.normalized)
    }
}

#[cfg(feature = "serde")]
impl Serialize for FullJid {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.inner.serialize(serializer)
    }
}

#[cfg(feature = "serde")]
impl Serialize for BareJid {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.inner.serialize(serializer)
    }
}

impl FromStr for FullJid {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::new(s)
    }
}

#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Jid {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        Jid::new(&s).map_err(de::Error::custom)
    }
}

#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for FullJid {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let jid = Jid::deserialize(deserializer)?;
        jid.try_into().map_err(de::Error::custom)
    }
}

#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for BareJid {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let jid = Jid::deserialize(deserializer)?;
        jid.try_into().map_err(de::Error::custom)
    }
}

#[cfg(feature = "quote")]
impl ToTokens for Jid {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let s = &self.normalized;
        tokens.extend(quote! {
            jid::Jid::new(#s).unwrap()
        });
    }
}

#[cfg(feature = "quote")]
impl ToTokens for FullJid {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let s = &self.inner.normalized;
        tokens.extend(quote! {
            jid::FullJid::new(#s).unwrap()
        });
    }
}

#[cfg(feature = "quote")]
impl ToTokens for BareJid {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let s = &self.inner.normalized;
        tokens.extend(quote! {
            jid::BareJid::new(#s).unwrap()
        });
    }
}

impl FullJid {
    /// Constructs a full Jabber ID containing all three components. This is of the form
    /// `node@domain/resource`, where node part is optional.
    /// If you want a non-fallible version, use [`FullJid::from_parts`] instead.
    ///
    /// # Examples
    ///
    /// ```
    /// use jid::FullJid;
    /// # use jid::Error;
    ///
    /// # fn main() -> Result<(), Error> {
    /// let jid = FullJid::new("node@domain/resource")?;
    ///
    /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
    /// assert_eq!(jid.domain().as_str(), "domain");
    /// assert_eq!(jid.resource().as_str(), "resource");
    /// # Ok(())
    /// # }
    /// ```
    pub fn new(unnormalized: &str) -> Result<Self, Error> {
        Jid::new(unnormalized)?.try_into()
    }

    /// Build a [`FullJid`] from typed parts. This method cannot fail because it uses parts that have
    /// already been parsed and stringprepped into [`NodePart`], [`DomainPart`], and [`ResourcePart`].
    /// This method allocates and does not consume the typed parts.
    pub fn from_parts(
        node: Option<&NodeRef>,
        domain: &DomainRef,
        resource: &ResourceRef,
    ) -> FullJid {
        let (at, slash, normalized);

        if let Some(node) = node {
            // Parts are never empty so len > 0 for NonZeroU16::new is always Some
            at = NonZeroU16::new(node.len() as u16);
            slash = NonZeroU16::new((node.len() + 1 + domain.len()) as u16);
            normalized = format!("{node}@{domain}/{resource}");
        } else {
            at = None;
            slash = NonZeroU16::new(domain.len() as u16);
            normalized = format!("{domain}/{resource}");
        }

        let inner = Jid {
            normalized,
            at,
            slash,
        };

        Self { inner }
    }

    /// The optional resource of the Jabber ID.  Since this is a full JID it is always present.
    pub fn resource(&self) -> &ResourceRef {
        self.inner.resource().unwrap()
    }

    /// Return the inner String of this full JID.
    pub fn into_inner(self) -> String {
        self.inner.into_inner()
    }

    /// Transforms this full JID into a [`BareJid`], throwing away the
    /// resource.
    ///
    /// ```
    /// # use jid::{BareJid, FullJid};
    /// let jid: FullJid = "foo@bar/baz".parse().unwrap();
    /// let bare = jid.into_bare();
    /// assert_eq!(bare.to_string(), "foo@bar");
    /// ```
    pub fn into_bare(self) -> BareJid {
        self.inner.into_bare()
    }
}

impl FromStr for BareJid {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::new(s)
    }
}

impl BareJid {
    /// Constructs a bare Jabber ID, containing two components. This is of the form
    /// `node`@`domain`, where node part is optional.
    /// If you want a non-fallible version, use [`BareJid::from_parts`] instead.
    ///
    /// # Examples
    ///
    /// ```
    /// use jid::BareJid;
    /// # use jid::Error;
    ///
    /// # fn main() -> Result<(), Error> {
    /// let jid = BareJid::new("node@domain")?;
    ///
    /// assert_eq!(jid.node().map(|x| x.as_str()), Some("node"));
    /// assert_eq!(jid.domain().as_str(), "domain");
    /// # Ok(())
    /// # }
    /// ```
    pub fn new(unnormalized: &str) -> Result<Self, Error> {
        Jid::new(unnormalized)?.try_into()
    }

    /// Build a [`BareJid`] from typed parts. This method cannot fail because it uses parts that have
    /// already been parsed and stringprepped into [`NodePart`] and [`DomainPart`].
    ///
    /// This method allocates and does not consume the typed parts. To avoid
    /// allocation if `node` is known to be `None` and `domain` is owned, you
    /// can use `domain.into()`.
    pub fn from_parts(node: Option<&NodeRef>, domain: &DomainRef) -> Self {
        let (at, normalized);

        if let Some(node) = node {
            // Parts are never empty so len > 0 for NonZeroU16::new is always Some
            at = NonZeroU16::new(node.len() as u16);
            normalized = format!("{node}@{domain}");
        } else {
            at = None;
            normalized = domain.to_string();
        }

        let inner = Jid {
            normalized,
            at,
            slash: None,
        };

        Self { inner }
    }

    /// Constructs a [`BareJid`] from the bare JID, by specifying a [`ResourcePart`].
    /// If you'd like to specify a stringy resource, use [`BareJid::with_resource_str`] instead.
    ///
    /// # Examples
    ///
    /// ```
    /// use jid::{BareJid, ResourcePart};
    ///
    /// let resource = ResourcePart::new("resource").unwrap();
    /// let bare = BareJid::new("node@domain").unwrap();
    /// let full = bare.with_resource(&resource);
    ///
    /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
    /// assert_eq!(full.domain().as_str(), "domain");
    /// assert_eq!(full.resource().as_str(), "resource");
    /// ```
    pub fn with_resource(&self, resource: &ResourceRef) -> FullJid {
        let slash = NonZeroU16::new(self.inner.normalized.len() as u16);
        let normalized = format!("{}/{resource}", self.inner.normalized);
        let inner = Jid {
            normalized,
            at: self.inner.at,
            slash,
        };

        FullJid { inner }
    }

    /// Constructs a [`FullJid`] from the bare JID, by specifying a stringy `resource`.
    /// If your resource has already been parsed into a [`ResourcePart`], use [`BareJid::with_resource`].
    ///
    /// # Examples
    ///
    /// ```
    /// use jid::BareJid;
    ///
    /// let bare = BareJid::new("node@domain").unwrap();
    /// let full = bare.with_resource_str("resource").unwrap();
    ///
    /// assert_eq!(full.node().map(|x| x.as_str()), Some("node"));
    /// assert_eq!(full.domain().as_str(), "domain");
    /// assert_eq!(full.resource().as_str(), "resource");
    /// ```
    pub fn with_resource_str(&self, resource: &str) -> Result<FullJid, Error> {
        let resource = ResourcePart::new(resource)?;
        Ok(self.with_resource(&resource))
    }

    /// Return the inner String of this bare JID.
    pub fn into_inner(self) -> String {
        self.inner.into_inner()
    }
}

#[cfg(feature = "minidom")]
impl IntoAttributeValue for Jid {
    fn into_attribute_value(self) -> Option<String> {
        Some(self.to_string())
    }
}

#[cfg(feature = "minidom")]
impl From<Jid> for Node {
    fn from(jid: Jid) -> Self {
        Node::Text(jid.to_string())
    }
}

#[cfg(feature = "minidom")]
impl IntoAttributeValue for FullJid {
    fn into_attribute_value(self) -> Option<String> {
        self.inner.into_attribute_value()
    }
}

#[cfg(feature = "minidom")]
impl From<FullJid> for Node {
    fn from(jid: FullJid) -> Self {
        jid.inner.into()
    }
}

#[cfg(feature = "minidom")]
impl IntoAttributeValue for BareJid {
    fn into_attribute_value(self) -> Option<String> {
        self.inner.into_attribute_value()
    }
}

#[cfg(feature = "minidom")]
impl From<BareJid> for Node {
    fn from(other: BareJid) -> Self {
        other.inner.into()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use alloc::{
        collections::{BTreeMap, BTreeSet},
        vec::Vec,
    };

    macro_rules! assert_size (
        ($t:ty, $sz:expr) => (
            assert_eq!(::core::mem::size_of::<$t>(), $sz);
        );
    );

    #[cfg(target_pointer_width = "32")]
    #[test]
    fn test_size() {
        assert_size!(BareJid, 16);
        assert_size!(FullJid, 16);
        assert_size!(Jid, 16);
    }

    #[cfg(target_pointer_width = "64")]
    #[test]
    fn test_size() {
        assert_size!(BareJid, 32);
        assert_size!(FullJid, 32);
        assert_size!(Jid, 32);
    }

    #[test]
    fn can_parse_full_jids() {
        assert_eq!(
            FullJid::from_str("a@b.c/d"),
            Ok(FullJid::new("a@b.c/d").unwrap())
        );
        assert_eq!(
            FullJid::from_str("b.c/d"),
            Ok(FullJid::new("b.c/d").unwrap())
        );

        assert_eq!(
            FullJid::from_str("a@b.c"),
            Err(Error::ResourceMissingInFullJid)
        );
        assert_eq!(
            FullJid::from_str("b.c"),
            Err(Error::ResourceMissingInFullJid)
        );
    }

    #[test]
    fn can_parse_bare_jids() {
        assert_eq!(
            BareJid::from_str("a@b.c"),
            Ok(BareJid::new("a@b.c").unwrap())
        );
        assert_eq!(BareJid::from_str("b.c"), Ok(BareJid::new("b.c").unwrap()));
    }

    #[test]
    fn can_parse_jids() {
        let full = FullJid::from_str("a@b.c/d").unwrap();
        let bare = BareJid::from_str("e@f.g").unwrap();

        assert_eq!(Jid::from_str("a@b.c/d").unwrap(), full);
        assert_eq!(Jid::from_str("e@f.g").unwrap(), bare);
    }

    #[test]
    fn full_to_bare_jid() {
        let bare: BareJid = FullJid::new("a@b.c/d").unwrap().to_bare();
        assert_eq!(bare, BareJid::new("a@b.c").unwrap());
    }

    #[test]
    fn bare_to_full_jid_str() {
        assert_eq!(
            BareJid::new("a@b.c")
                .unwrap()
                .with_resource_str("d")
                .unwrap(),
            FullJid::new("a@b.c/d").unwrap()
        );
    }

    #[test]
    fn bare_to_full_jid() {
        assert_eq!(
            BareJid::new("a@b.c")
                .unwrap()
                .with_resource(&ResourcePart::new("d").unwrap()),
            FullJid::new("a@b.c/d").unwrap()
        )
    }

    #[test]
    fn node_from_jid() {
        let jid = Jid::new("a@b.c/d").unwrap();

        assert_eq!(jid.node().map(|x| x.as_str()), Some("a"),);
    }

    #[test]
    fn domain_from_jid() {
        let jid = Jid::new("a@b.c").unwrap();

        assert_eq!(jid.domain().as_str(), "b.c");
    }

    #[test]
    fn resource_from_jid() {
        let jid = Jid::new("a@b.c/d").unwrap();

        assert_eq!(jid.resource().map(|x| x.as_str()), Some("d"),);
    }

    #[test]
    fn jid_to_full_bare() {
        let full = FullJid::new("a@b.c/d").unwrap();
        let bare = BareJid::new("a@b.c").unwrap();

        assert_eq!(FullJid::try_from(Jid::from(full.clone())), Ok(full.clone()));
        assert_eq!(
            FullJid::try_from(Jid::from(bare.clone())),
            Err(Error::ResourceMissingInFullJid),
        );
        assert_eq!(Jid::from(full.clone().to_bare()), bare.clone());
        assert_eq!(Jid::from(bare.clone()), bare);
    }

    #[test]
    fn serialise() {
        assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
        assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
    }

    #[test]
    fn hash() {
        let _map: BTreeMap<Jid, String> = BTreeMap::new();
    }

    #[test]
    fn invalid_jids() {
        assert_eq!(BareJid::from_str(""), Err(Error::Idna));
        assert_eq!(BareJid::from_str("/c"), Err(Error::Idna));
        assert_eq!(BareJid::from_str("a@/c"), Err(Error::Idna));
        assert_eq!(BareJid::from_str("@b"), Err(Error::NodeEmpty));
        assert_eq!(BareJid::from_str("b/"), Err(Error::ResourceEmpty));

        assert_eq!(FullJid::from_str(""), Err(Error::Idna));
        assert_eq!(FullJid::from_str("/c"), Err(Error::Idna));
        assert_eq!(FullJid::from_str("a@/c"), Err(Error::Idna));
        assert_eq!(FullJid::from_str("@b"), Err(Error::NodeEmpty));
        assert_eq!(FullJid::from_str("b/"), Err(Error::ResourceEmpty));
        assert_eq!(
            FullJid::from_str("a@b"),
            Err(Error::ResourceMissingInFullJid)
        );
        assert_eq!(BareJid::from_str("a@b/c"), Err(Error::ResourceInBareJid));
        assert_eq!(BareJid::from_str("a@b@c"), Err(Error::TooManyAts));
        assert_eq!(FullJid::from_str("a@b@c/d"), Err(Error::TooManyAts));
    }

    #[test]
    fn display_jids() {
        assert_eq!(FullJid::new("a@b/c").unwrap().to_string(), "a@b/c");
        assert_eq!(BareJid::new("a@b").unwrap().to_string(), "a@b");
        assert_eq!(
            Jid::from(FullJid::new("a@b/c").unwrap()).to_string(),
            "a@b/c"
        );
        assert_eq!(Jid::from(BareJid::new("a@b").unwrap()).to_string(), "a@b");
    }

    #[cfg(feature = "minidom")]
    #[test]
    fn minidom() {
        let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
        assert_eq!(to, Jid::from(FullJid::new("a@b/c").unwrap()));

        let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
        let to: Jid = elem.attr("from").unwrap().parse().unwrap();
        assert_eq!(to, Jid::from(BareJid::new("a@b").unwrap()));

        let elem: minidom::Element = "<message xmlns='ns1' from='a@b/c'/>".parse().unwrap();
        let to: FullJid = elem.attr("from").unwrap().parse().unwrap();
        assert_eq!(to, FullJid::new("a@b/c").unwrap());

        let elem: minidom::Element = "<message xmlns='ns1' from='a@b'/>".parse().unwrap();
        let to: BareJid = elem.attr("from").unwrap().parse().unwrap();
        assert_eq!(to, BareJid::new("a@b").unwrap());
    }

    #[cfg(feature = "minidom")]
    #[test]
    fn minidom_into_attr() {
        let full = FullJid::new("a@b/c").unwrap();
        let elem = minidom::Element::builder("message", "jabber:client")
            .attr("from".try_into().unwrap(), full.clone())
            .build();
        assert_eq!(elem.attr("from"), Some(full.to_string().as_str()));

        let bare = BareJid::new("a@b").unwrap();
        let elem = minidom::Element::builder("message", "jabber:client")
            .attr("from".try_into().unwrap(), bare.clone())
            .build();
        assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));

        let jid = Jid::from(bare.clone());
        let _elem = minidom::Element::builder("message", "jabber:client")
            .attr("from".try_into().unwrap(), jid)
            .build();
        assert_eq!(elem.attr("from"), Some(bare.to_string().as_str()));
    }

    #[test]
    fn stringprep() {
        let full = FullJid::from_str("Test@☃.coM/Test™").unwrap();
        let equiv = FullJid::new("test@☃.com/TestTM").unwrap();
        assert_eq!(full, equiv);
    }

    #[test]
    fn invalid_stringprep() {
        FullJid::from_str("a@b/🎉").unwrap_err();
    }

    #[test]
    fn idna() {
        let bare = BareJid::from_str("Weiß.com.").unwrap();
        let equiv = BareJid::new("weiss.com").unwrap();
        assert_eq!(bare, equiv);
        BareJid::from_str("127.0.0.1").unwrap();
        BareJid::from_str("[::1]").unwrap();
        BareJid::from_str("domain.tld.").unwrap();
    }

    #[test]
    fn invalid_idna() {
        BareJid::from_str("a@b@c").unwrap_err();
        FullJid::from_str("a@b@c/d").unwrap_err();
        BareJid::from_str("[::1234").unwrap_err();
        BareJid::from_str("1::1234]").unwrap_err();
        BareJid::from_str("domain.tld:5222").unwrap_err();
        BareJid::from_str("-domain.tld").unwrap_err();
        BareJid::from_str("domain.tld-").unwrap_err();
        BareJid::from_str("domain..tld").unwrap_err();
        BareJid::from_str("domain.tld..").unwrap_err();
        BareJid::from_str("1234567890123456789012345678901234567890123456789012345678901234.com")
            .unwrap_err();
    }

    #[test]
    fn jid_from_parts() {
        let node = NodePart::new("node").unwrap();
        let domain = DomainPart::new("domain").unwrap();
        let resource = ResourcePart::new("resource").unwrap();

        let jid = Jid::from_parts(Some(&node), &domain, Some(&resource));
        assert_eq!(jid, Jid::new("node@domain/resource").unwrap());

        let barejid = BareJid::from_parts(Some(&node), &domain);
        assert_eq!(barejid, BareJid::new("node@domain").unwrap());

        let fulljid = FullJid::from_parts(Some(&node), &domain, &resource);
        assert_eq!(fulljid, FullJid::new("node@domain/resource").unwrap());
    }

    #[test]
    #[cfg(feature = "serde")]
    fn jid_ser_de() {
        let jid: Jid = Jid::new("node@domain").unwrap();
        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);

        let jid: Jid = Jid::new("node@domain/resource").unwrap();
        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);

        let jid: BareJid = BareJid::new("node@domain").unwrap();
        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain")]);

        let jid: FullJid = FullJid::new("node@domain/resource").unwrap();
        serde_test::assert_tokens(&jid, &[serde_test::Token::Str("node@domain/resource")]);
    }

    #[test]
    fn jid_into_parts_and_from_parts() {
        let node = NodePart::new("node").unwrap();
        let domain = DomainPart::new("domain").unwrap();

        let jid1 = domain.with_node(&node);
        let jid2 = node.with_domain(&domain);
        let jid3 = BareJid::new("node@domain").unwrap();
        assert_eq!(jid1, jid2);
        assert_eq!(jid2, jid3);
    }

    #[test]
    fn jid_match_replacement_try_as() {
        let jid1 = Jid::new("foo@bar").unwrap();
        let jid2 = Jid::new("foo@bar/baz").unwrap();

        match jid1.try_as_full() {
            Err(_) => (),
            other => panic!("unexpected result: {:?}", other),
        };

        match jid2.try_as_full() {
            Ok(_) => (),
            other => panic!("unexpected result: {:?}", other),
        };
    }

    #[test]
    fn jid_match_replacement_try_as_mut() {
        let mut jid1 = Jid::new("foo@bar").unwrap();
        let mut jid2 = Jid::new("foo@bar/baz").unwrap();

        match jid1.try_as_full_mut() {
            Err(_) => (),
            other => panic!("unexpected result: {:?}", other),
        };

        match jid2.try_as_full_mut() {
            Ok(_) => (),
            other => panic!("unexpected result: {:?}", other),
        };
    }

    #[test]
    fn jid_match_replacement_try_into() {
        let jid1 = Jid::new("foo@bar").unwrap();
        let jid2 = Jid::new("foo@bar/baz").unwrap();

        match jid1.try_as_full() {
            Err(_) => (),
            other => panic!("unexpected result: {:?}", other),
        };

        match jid2.try_as_full() {
            Ok(_) => (),
            other => panic!("unexpected result: {:?}", other),
        };
    }

    #[test]
    fn lookup_jid_by_full_jid() {
        let mut map: BTreeSet<Jid> = BTreeSet::new();
        let jid1 = Jid::new("foo@bar").unwrap();
        let jid2 = Jid::new("foo@bar/baz").unwrap();
        let jid3 = FullJid::new("foo@bar/baz").unwrap();

        map.insert(jid1);
        assert!(!map.contains(&jid2));
        assert!(!map.contains(&jid3));
        map.insert(jid2);
        assert!(map.contains(&jid3));
    }

    #[test]
    fn lookup_full_jid_by_jid() {
        let mut map: BTreeSet<FullJid> = BTreeSet::new();
        let jid1 = FullJid::new("foo@bar/baz").unwrap();
        let jid2 = FullJid::new("foo@bar/fnord").unwrap();
        let jid3 = Jid::new("foo@bar/fnord").unwrap();

        map.insert(jid1);
        assert!(!map.contains(&jid2));
        assert!(!map.contains(&jid3));
        map.insert(jid2);
        assert!(map.contains(&jid3));
    }

    #[test]
    fn lookup_bare_jid_by_jid() {
        let mut map: BTreeSet<BareJid> = BTreeSet::new();
        let jid1 = BareJid::new("foo@bar").unwrap();
        let jid2 = BareJid::new("foo@baz").unwrap();
        let jid3 = Jid::new("foo@baz").unwrap();

        map.insert(jid1);
        assert!(!map.contains(&jid2));
        assert!(!map.contains(&jid3));
        map.insert(jid2);
        assert!(map.contains(&jid3));
    }

    #[test]
    fn normalizes_all_parts() {
        assert_eq!(
            Jid::new("ßA@IX.test/\u{2168}").unwrap().as_str(),
            "ssa@ix.test/IX"
        );
    }

    #[test]
    fn rejects_unassigned_codepoints() {
        match Jid::new("\u{01f601}@example.com") {
            Err(Error::NodePrep) => (),
            other => panic!("unexpected result: {:?}", other),
        };

        match Jid::new("foo@\u{01f601}.example.com") {
            Err(Error::NamePrep) => (),
            other => panic!("unexpected result: {:?}", other),
        };

        match Jid::new("foo@example.com/\u{01f601}") {
            Err(Error::ResourcePrep) => (),
            other => panic!("unexpected result: {:?}", other),
        };
    }

    #[test]
    fn accepts_domain_only_jid() {
        match Jid::new("example.com") {
            Ok(_) => (),
            other => panic!("unexpected result: {:?}", other),
        };

        match BareJid::new("example.com") {
            Ok(_) => (),
            other => panic!("unexpected result: {:?}", other),
        };

        match FullJid::new("example.com/x") {
            Ok(_) => (),
            other => panic!("unexpected result: {:?}", other),
        };
    }

    #[test]
    fn is_bare_returns_true_iff_bare() {
        let bare = Jid::new("foo@bar").unwrap();
        let full = Jid::new("foo@bar/baz").unwrap();

        assert!(bare.is_bare());
        assert!(!full.is_bare());
    }

    #[test]
    fn is_full_returns_true_iff_full() {
        let bare = Jid::new("foo@bar").unwrap();
        let full = Jid::new("foo@bar/baz").unwrap();

        assert!(!bare.is_full());
        assert!(full.is_full());
    }

    #[test]
    fn reject_long_localpart() {
        let mut long = Vec::with_capacity(1028);
        long.resize(1024, b'a');
        let mut long = String::from_utf8(long).unwrap();
        long.push_str("@foo");

        match Jid::new(&long) {
            Err(Error::NodeTooLong) => (),
            other => panic!("unexpected result: {:?}", other),
        }

        match BareJid::new(&long) {
            Err(Error::NodeTooLong) => (),
            other => panic!("unexpected result: {:?}", other),
        }
    }

    #[test]
    fn reject_long_domainpart() {
        let mut long = Vec::with_capacity(66);
        long.push(b'x');
        long.push(b'@');
        long.resize(66, b'a');
        let long = String::from_utf8(long).unwrap();

        Jid::new(&long).unwrap_err();
        BareJid::new(&long).unwrap_err();

        // A domain can be up to 253 bytes.
        let mut long = Vec::with_capacity(256);
        long.push(b'x');
        long.push(b'@');
        long.resize(65, b'a');
        long.push(b'.');
        long.resize(129, b'b');
        long.push(b'.');
        long.resize(193, b'c');
        long.push(b'.');
        long.resize(256, b'd');
        let long = String::from_utf8(long).unwrap();

        Jid::new(&long).unwrap_err();
        BareJid::new(&long).unwrap_err();
    }

    #[test]
    fn reject_long_resourcepart() {
        let mut long = Vec::with_capacity(1028);
        long.push(b'x');
        long.push(b'@');
        long.push(b'y');
        long.push(b'/');
        long.resize(1028, b'a');
        let long = String::from_utf8(long).unwrap();

        match Jid::new(&long) {
            Err(Error::ResourceTooLong) => (),
            other => panic!("unexpected result: {:?}", other),
        }

        match FullJid::new(&long) {
            Err(Error::ResourceTooLong) => (),
            other => panic!("unexpected result: {:?}", other),
        }
    }
}
