Skip to content

Commit

Permalink
Make discriminator optional and non-zero in preparation for new usern…
Browse files Browse the repository at this point in the history
…ames
  • Loading branch information
Xaeroxe committed May 4, 2023
1 parent 891cd1b commit edce406
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 121 deletions.
1 change: 1 addition & 0 deletions src/cache/mod.rs
Expand Up @@ -978,6 +978,7 @@ impl Default for Cache {
#[cfg(test)]
mod test {
use std::collections::HashMap;
use std::num::NonZeroU16;

use crate::cache::{Cache, CacheUpdate, Settings};
use crate::model::prelude::*;
Expand Down
6 changes: 4 additions & 2 deletions src/model/channel/message.rs
Expand Up @@ -365,8 +365,10 @@ impl Message {
let mut at_distinct = String::with_capacity(38);
at_distinct.push('@');
at_distinct.push_str(&u.name);
at_distinct.push('#');
write!(at_distinct, "{:04}", u.discriminator).unwrap();
if let Some(discriminator) = u.discriminator {
at_distinct.push('#');
write!(at_distinct, "{:04}", discriminator.get()).unwrap();
}

let mut m = u.mention().to_string();
// Check whether we're replacing a nickname mention or a normal mention.
Expand Down
2 changes: 2 additions & 0 deletions src/model/channel/mod.rs
Expand Up @@ -465,6 +465,8 @@ pub struct ThreadsData {
mod test {
#[cfg(all(feature = "model", feature = "utils"))]
mod model_utils {
use std::num::NonZeroU16;

use crate::model::prelude::*;

#[test]
Expand Down
5 changes: 0 additions & 5 deletions src/model/error.rs
Expand Up @@ -28,11 +28,6 @@ use super::Permissions;
/// #[serenity::async_trait]
/// impl EventHandler for Handler {
/// async fn guild_ban_removal(&self, context: Context, guild_id: GuildId, user: User) {
/// // If the user has an even discriminator, don't re-ban them.
/// if user.discriminator % 2 == 0 {
/// return;
/// }
///
/// match guild_id.ban(&context, user, 8).await {
/// Ok(()) => {
/// // Ban successful.
Expand Down
10 changes: 6 additions & 4 deletions src/model/gateway.rs
@@ -1,6 +1,8 @@
//! Models pertaining to the gateway.

use serde::ser::SerializeSeq;
use std::num::NonZeroU16;

use url::Url;

use super::prelude::*;
Expand Down Expand Up @@ -232,8 +234,8 @@ pub struct PresenceUser {
pub id: UserId,
pub avatar: Option<String>,
pub bot: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none", with = "discriminator::option")]
pub discriminator: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none", with = "discriminator")]
pub discriminator: Option<NonZeroU16>,
pub email: Option<String>,
pub mfa_enabled: Option<bool>,
#[serde(rename = "username")]
Expand All @@ -251,7 +253,7 @@ impl PresenceUser {
Some(User {
avatar: self.avatar,
bot: self.bot?,
discriminator: self.discriminator?,
discriminator: self.discriminator,
id: self.id,
name: self.name?,
public_flags: self.public_flags,
Expand Down Expand Up @@ -285,7 +287,7 @@ impl PresenceUser {
self.avatar = Some(avatar.clone());
}
self.bot = Some(user.bot);
self.discriminator = Some(user.discriminator);
self.discriminator = user.discriminator;
self.name = Some(user.name.clone());
if let Some(public_flags) = user.public_flags {
self.public_flags = Some(public_flags);
Expand Down
6 changes: 5 additions & 1 deletion src/model/guild/member.rs
Expand Up @@ -233,7 +233,11 @@ impl Member {
#[inline]
#[must_use]
pub fn distinct(&self) -> String {
format!("{}#{:04}", self.display_name(), self.user.discriminator)
if let Some(discriminator) = self.user.discriminator {
format!("{}#{:04}", self.display_name(), discriminator.get())
} else {
self.display_name().to_string()
}
}

/// Edits the member in place with the given data.
Expand Down
2 changes: 2 additions & 0 deletions src/model/mention.rs
Expand Up @@ -183,6 +183,8 @@ mentionable!(value: Role, value.id);
#[cfg(feature = "utils")]
#[cfg(test)]
mod test {
use std::num::NonZeroU16;

use crate::model::prelude::*;

#[test]
Expand Down
161 changes: 63 additions & 98 deletions src/model/user.rs
Expand Up @@ -5,6 +5,7 @@ use std::fmt;
use std::fmt::Write;
#[cfg(feature = "temp_cache")]
use std::sync::Arc;
use std::num::NonZeroU16;

use serde::{Deserialize, Serialize};

Expand All @@ -24,45 +25,36 @@ use crate::internal::prelude::*;
use crate::json::json;
use crate::json::to_string;
use crate::model::mention::Mentionable;

/// Used with `#[serde(with|deserialize_with|serialize_with)]`
///
/// # Examples
///
/// ```rust,ignore
/// use std::num::NonZeroU16;
///
/// #[derive(Deserialize, Serialize)]
/// struct A {
/// #[serde(with = "discriminator")]
/// id: u16,
/// id: Option<NonZeroU16>,
/// }
///
/// #[derive(Deserialize)]
/// struct B {
/// #[serde(deserialize_with = "discriminator::deserialize")]
/// id: u16,
/// id: Option<NonZeroU16>,
/// }
///
/// #[derive(Serialize)]
/// struct C {
/// #[serde(serialize_with = "discriminator::serialize")]
/// id: u16,
/// id: Option<NonZeroU16>,
/// }
/// ```
pub(crate) mod discriminator {
use std::convert::TryFrom;
use std::fmt;

use serde::de::{Error, Visitor};
use serde::{Deserializer, Serializer};

pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u16, D::Error> {
deserializer.deserialize_any(DiscriminatorVisitor)
}

#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn serialize<S: Serializer>(value: &u16, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(&format_args!("{value:04}"))
}

struct DiscriminatorVisitor;

Expand All @@ -82,46 +74,19 @@ pub(crate) mod discriminator {
}
}

/// Used with `#[serde(with|deserialize_with|serialize_with)]`
///
/// # Examples
///
/// ```rust,ignore
/// #[derive(Deserialize, Serialize)]
/// struct A {
/// #[serde(with = "discriminator::option")]
/// id: Option<u16>,
/// }
///
/// #[derive(Deserialize)]
/// struct B {
/// #[serde(deserialize_with = "discriminator::option::deserialize")]
/// id: Option<u16>,
/// }
///
/// #[derive(Serialize)]
/// struct C {
/// #[serde(serialize_with = "discriminator::option::serialize")]
/// id: Option<u16>,
/// }
/// ```
pub(crate) mod option {
use std::fmt;

use serde::de::{Error, Visitor};
use serde::{Deserializer, Serializer};
use std::num::NonZeroU16;

use super::DiscriminatorVisitor;
use serde::{Deserializer, Serializer};

pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<u16>, D::Error> {
deserializer.deserialize_option(OptionalDiscriminatorVisitor)
}
pub fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<NonZeroU16>, D::Error> {
deserializer.deserialize_option(OptionalDiscriminatorVisitor)
}

#[allow(clippy::trivially_copy_pass_by_ref)]
pub fn serialize<S: Serializer>(
value: &Option<u16>,
value: &Option<NonZeroU16>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match value {
Expand All @@ -130,29 +95,28 @@ pub(crate) mod discriminator {
}
}

struct OptionalDiscriminatorVisitor;
struct OptionalDiscriminatorVisitor;

impl<'de> Visitor<'de> for OptionalDiscriminatorVisitor {
type Value = Option<u16>;
impl<'de> Visitor<'de> for OptionalDiscriminatorVisitor {
type Value = Option<NonZeroU16>;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("optional string or integer discriminator")
}
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("optional string or integer discriminator")
}

fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
Ok(None)
}

fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
Ok(None)
}

fn visit_some<D: Deserializer<'de>>(
self,
deserializer: D,
) -> Result<Self::Value, D::Error> {
Ok(Some(deserializer.deserialize_any(DiscriminatorVisitor)?))
}
fn visit_some<D: Deserializer<'de>>(
self,
deserializer: D,
) -> Result<Self::Value, D::Error> {
deserializer.deserialize_any(DiscriminatorVisitor).map(NonZeroU16::new)
}
}
}
Expand Down Expand Up @@ -277,14 +241,17 @@ impl OnlineStatus {
pub struct User {
/// The unique Id of the user. Can be used to calculate the account's creation date.
pub id: UserId,
/// The account's username. Changing username will trigger a discriminator change if the
/// username+discriminator pair becomes non-unique.
/// The account's username. Changing username will trigger a discriminator
/// change if the username+discriminator pair becomes non-unique. Unless the account has
/// migrated to a next generation username, which does not have a discriminant.
#[serde(rename = "username")]
pub name: String,
/// The account's discriminator to differentiate the user from others with the same
/// [`Self::name`]. The name+discriminator pair is always unique.
#[serde(with = "discriminator")]
pub discriminator: u16,
/// The account's discriminator to differentiate the user from others with
/// the same [`Self::name`]. The name+discriminator pair is always unique.
/// If the discriminator is not present, then this is a next generation username
/// which is implicitly unique.
#[serde(default, skip_serializing_if = "Option::is_none", with = "discriminator")]
pub discriminator: Option<NonZeroU16>,
/// Optional avatar hash.
pub avatar: Option<String>,
/// Indicator of whether the user is a bot.
Expand Down Expand Up @@ -838,8 +805,13 @@ fn avatar_url(user_id: UserId, hash: Option<&String>) -> Option<String> {
}

#[cfg(feature = "model")]
fn default_avatar_url(discriminator: u16) -> String {
cdn!("/embed/avatars/{}.png", discriminator % 5u16)
fn default_avatar_url(discriminator: Option<NonZeroU16>) -> String {
if let Some(discriminator) = discriminator {
cdn!("/embed/avatars/{}.png", discriminator.get() % 5u16)
} else {
// TODO: Replace this with a correct implementation once Discord publishes how this is going to work.
cdn!("/embed/avatars/0.png").to_string()
}
}

#[cfg(feature = "model")]
Expand All @@ -857,20 +829,23 @@ fn banner_url(user_id: UserId, hash: Option<&String>) -> Option<String> {
}

#[cfg(feature = "model")]
fn tag(name: &str, discriminator: u16) -> String {
fn tag(name: &str, discriminator: Option<NonZeroU16>) -> String {
// 32: max length of username
// 1: `#`
// 4: max length of discriminator
let mut tag = String::with_capacity(37);
tag.push_str(name);
tag.push('#');
write!(tag, "{discriminator:04}").unwrap();

if let Some(discriminator) = discriminator {
tag.push('#');
write!(tag, "{discriminator:04}").unwrap();
}
tag
}

#[cfg(test)]
mod test {
use std::num::NonZeroU16;

#[test]
fn test_discriminator_serde() {
use serde::{Deserialize, Serialize};
Expand All @@ -880,17 +855,8 @@ mod test {

#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct User {
#[serde(with = "discriminator")]
discriminator: u16,
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
struct UserOpt {
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "discriminator::option"
)]
discriminator: Option<u16>,
#[serde(default, skip_serializing_if = "Option::is_none", with = "discriminator")]
discriminator: Option<NonZeroU16>,
}

let user = User {
Expand All @@ -912,6 +878,8 @@ mod test {
#[cfg(feature = "model")]
mod model {
use crate::model::id::UserId;
use std::num::NonZeroU16;

use crate::model::user::User;

#[test]
Expand Down Expand Up @@ -942,19 +910,16 @@ mod test {

#[test]
fn default_avatars() {
let mut user = User {
discriminator: 0,
..Default::default()
};
let mut user = User { discriminator: None, ..Default::default() };

assert!(user.default_avatar_url().ends_with("0.png"));
user.discriminator = 1;
user.discriminator = NonZeroU16::new(1);
assert!(user.default_avatar_url().ends_with("1.png"));
user.discriminator = 2;
user.discriminator = NonZeroU16::new(2);
assert!(user.default_avatar_url().ends_with("2.png"));
user.discriminator = 3;
user.discriminator = NonZeroU16::new(3);
assert!(user.default_avatar_url().ends_with("3.png"));
user.discriminator = 4;
user.discriminator = NonZeroU16::new(4);
assert!(user.default_avatar_url().ends_with("4.png"));
}
}
Expand Down

0 comments on commit edce406

Please sign in to comment.