<commit_before>//! Matrix user identifiers.

use std::{borrow::Cow, convert::TryFrom, num::NonZeroU8};

#[cfg(feature = "diesel")]
use diesel::sql_types::Text;

use crate::{error::Error, is_valid_server_name, parse_id};

/// A Matrix user ID.
///
/// A `UserId` is generated randomly or converted from a string slice, and can be converted back
/// into a string as needed.
///
/// ```
/// # use std::convert::TryFrom;
/// # use ruma_identifiers::UserId;
/// assert_eq!(
///     UserId::try_from("@carl:example.com").unwrap().as_ref(),
///     "@carl:example.com"
/// );
/// ```
#[derive(Clone, Debug)]
#[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))]
#[cfg_attr(feature = "diesel", sql_type = "Text")]
pub struct UserId {
    full_id: String,
    colon_idx: NonZeroU8,
    /// Whether this user id is a historical one.
    ///
    /// A historical user id is one that is not legal per the regular user id rules, but was
    /// accepted by previous versions of the spec and thus has to be supported because users with
    /// these kinds of ids still exist.
    is_historical: bool,
}

impl UserId {
    /// Attempts to generate a `UserId` for the given origin server with a localpart consisting of
    /// 12 random ASCII characters.
    ///
    /// Fails if the given homeserver cannot be parsed as a valid host.
    #[cfg(feature = "rand")]
    #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
    pub fn new(server_name: &str) -> Result<Self, Error> {
        use crate::generate_localpart;

        if !is_valid_server_name(server_name) {
            return Err(Error::InvalidServerName);
        }
        let full_id = format!("@{}:{}", generate_localpart(12).to_lowercase(), server_name);

        Ok(Self {
            full_id,
            colon_idx: NonZeroU8::new(13).unwrap(),
            is_historical: false,
        })
    }

    /// Attempts to complete a user ID, by adding the colon + server name and `@` prefix, if not
    /// present already.
    ///
    /// This is a convenience function for the login API, where a user can supply either their full
    /// user ID or just the localpart. It only supports a valid user ID or a valid user ID
    /// localpart, not the localpart plus the `@` prefix, or the localpart plus server name without
    /// the `@` prefix.
    pub fn parse_with_server_name<'a>(
        id: impl Into<Cow<'a, str>>,
        server_name: &str,
    ) -> Result<Self, Error> {
        let id = id.into();
        if id.starts_with('@') {
            Self::try_from(id)
        } else {
            let is_fully_conforming = localpart_is_fully_comforming(&id)?;
            if !is_valid_server_name(server_name) {
                return Err(Error::InvalidServerName);
            }

            Ok(Self {
                full_id: format!("@{}:{}", id, server_name),
                colon_idx: NonZeroU8::new(id.len() as u8 + 1).unwrap(),
                is_historical: !is_fully_conforming,
            })
        }
    }

    /// Returns the user's localpart.
    pub fn localpart(&self) -> &str {
        &self.full_id[1..self.colon_idx.get() as usize]
    }

    /// Returns the server name of the user ID.
    pub fn server_name(&self) -> &str {
        &self.full_id[self.colon_idx.get() as usize + 1..]
    }

    /// Whether this user ID is a historical one, i.e. one that doesn't conform to the latest
    /// specification of the user ID grammar but is still accepted because it was previously
    /// allowed.
    pub fn is_historical(&self) -> bool {
        self.is_historical<commit_msg>Update parse_with_server_name's id bounds<commit_after>//! Matrix user identifiers.

use std::{borrow::Cow, convert::TryFrom, num::NonZeroU8};

#[cfg(feature = "diesel")]
use diesel::sql_types::Text;

use crate::{error::Error, is_valid_server_name, parse_id};

/// A Matrix user ID.
///
/// A `UserId` is generated randomly or converted from a string slice, and can be converted back
/// into a string as needed.
///
/// ```
/// # use std::convert::TryFrom;
/// # use ruma_identifiers::UserId;
/// assert_eq!(
///     UserId::try_from("@carl:example.com").unwrap().as_ref(),
///     "@carl:example.com"
/// );
/// ```
#[derive(Clone, Debug)]
#[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))]
#[cfg_attr(feature = "diesel", sql_type = "Text")]
pub struct UserId {
    full_id: String,
    colon_idx: NonZeroU8,
    /// Whether this user id is a historical one.
    ///
    /// A historical user id is one that is not legal per the regular user id rules, but was
    /// accepted by previous versions of the spec and thus has to be supported because users with
    /// these kinds of ids still exist.
    is_historical: bool,
}

impl UserId {
    /// Attempts to generate a `UserId` for the given origin server with a localpart consisting of
    /// 12 random ASCII characters.
    ///
    /// Fails if the given homeserver cannot be parsed as a valid host.
    #[cfg(feature = "rand")]
    #[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
    pub fn new(server_name: &str) -> Result<Self, Error> {
        use crate::generate_localpart;

        if !is_valid_server_name(server_name) {
            return Err(Error::InvalidServerName);
        }
        let full_id = format!("@{}:{}", generate_localpart(12).to_lowercase(), server_name);

        Ok(Self {
            full_id,
            colon_idx: NonZeroU8::new(13).unwrap(),
            is_historical: false,
        })
    }

    /// Attempts to complete a user ID, by adding the colon + server name and `@` prefix, if not
    /// present already.
    ///
    /// This is a convenience function for the login API, where a user can supply either their full
    /// user ID or just the localpart. It only supports a valid user ID or a valid user ID
    /// localpart, not the localpart plus the `@` prefix, or the localpart plus server name without
    /// the `@` prefix.
    pub fn parse_with_server_name(
        id: impl AsRef<str> + Into<String>,
        server_name: &str,
    ) -> Result<Self, Error> {
        let id_str = id.as_ref();

        if id_str.starts_with('@') {
            Self::try_from(id.into())
        } else {
            let is_fully_conforming = localpart_is_fully_comforming(id_str)?;
            if !is_valid_server_name(server_name) {
                return Err(Error::InvalidServerName);
            }

            Ok(Self {
                full_id: format!("@{}:{}", id_str, server_name),
                colon_idx: NonZeroU8::new(id_str.len() as u8 + 1).unwrap(),
                is_historical: !is_fully_conforming,
            })
        }
    }

    /// Returns the user's localpart.
    pub fn localpart(&self) -> &str {
        &self.full_id[1..self.colon_idx.get() as usize]
    }

    /// Returns the server name of the user ID.
    pub fn server_name(&self) -> &str {
        &self.full_id[self.colon_idx.get() as usize + 1..]
    }

    /// Whether this user ID is a historical one, i.e. one that doesn't conform to the latest
    /// specification of the user ID grammar but is still accepted because it was previously
    /// allowed.
    pub fn is_historical(&self) -> bool {
        self.is_historical
{'fraction_non_alphanumeric': 0.13352745424292845, 'fraction_numerical': 0.003882418191902385, 'mean_word_length': 2.446249402771142, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1723442', 'n_tokens_mistral': 2231, 'n_tokens_neox': 2204, 'n_words': 698}