Skip to content

Commit

Permalink
Make builders work & add serialization workarounds
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesbt365 committed May 22, 2024
1 parent 1b15acf commit 64dc636
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 15 deletions.
69 changes: 59 additions & 10 deletions src/builder/edit_onboarding/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
#[cfg(feature = "http")]

Check failure on line 1 in src/builder/edit_onboarding/mod.rs

View workflow job for this annotation

GitHub Actions / Format

Diff in /home/runner/work/serenity/serenity/src/builder/edit_onboarding/mod.rs
use super::Builder;

use crate::model::guild::OnboardingMode;
use crate::model::id::ChannelId;

#[cfg(feature = "http")]
use crate::model::id::GuildId;
#[cfg(feature = "http")]
use crate::model::Permissions;

Check failure on line 10 in src/builder/edit_onboarding/mod.rs

View workflow job for this annotation

GitHub Actions / Format

Diff in /home/runner/work/serenity/serenity/src/builder/edit_onboarding/mod.rs
#[cfg(feature = "http")]
use crate::http::CacheHttp;
#[cfg(feature = "http")]
use crate::model::guild::Onboarding;
#[cfg(feature = "http")]
use crate::internal::prelude::*;

mod prompt_option_structure;
mod prompt_structure;

Expand Down Expand Up @@ -32,17 +47,20 @@ use sealed::*;

#[derive(serde::Serialize, Clone, Debug)]
#[must_use = "Builders do nothing unless built"]
pub struct EditOnboarding<Stage: Sealed> {
pub struct EditOnboarding<'a, Stage: Sealed> {
prompts: Vec<CreateOnboardingPrompt<prompt_structure::Ready>>,
default_channel_ids: Vec<ChannelId>,
enabled: bool,
mode: OnboardingMode,

#[serde(skip)]
audit_log_reason: Option<&'a str>,

#[serde(skip)]
_stage: Stage,
}

impl Default for EditOnboarding<NeedsPrompts> {
impl<'a> Default for EditOnboarding<'a, NeedsPrompts> {
/// See the documentation of [`Self::new`].
fn default() -> Self {
// Producing dummy values is okay as we must transition through all `Stage`s before firing,
Expand All @@ -52,70 +70,101 @@ impl Default for EditOnboarding<NeedsPrompts> {
default_channel_ids: Vec::new(),
enabled: true,
mode: OnboardingMode::default(),
audit_log_reason: None,

_stage: NeedsPrompts,
}
}
}

impl EditOnboarding<NeedsPrompts> {
impl<'a> EditOnboarding<'a, NeedsPrompts> {
pub fn new() -> Self {
Self::default()
}

pub fn prompts(
self,
prompts: Vec<CreateOnboardingPrompt<prompt_structure::Ready>>,
) -> EditOnboarding<NeedsChannels> {
) -> EditOnboarding<'a, NeedsChannels> {
EditOnboarding {
prompts,
default_channel_ids: self.default_channel_ids,
enabled: self.enabled,
mode: self.mode,
audit_log_reason: self.audit_log_reason,

_stage: NeedsChannels,
}
}
}

impl EditOnboarding<NeedsChannels> {
impl<'a> EditOnboarding<'a, NeedsChannels> {
pub fn default_channels(
self,
default_channel_ids: Vec<ChannelId>,
) -> EditOnboarding<NeedsEnabled> {
) -> EditOnboarding<'a, NeedsEnabled> {
EditOnboarding {
prompts: self.prompts,
default_channel_ids,
enabled: self.enabled,
mode: self.mode,
audit_log_reason: self.audit_log_reason,

_stage: NeedsEnabled,
}
}
}

impl EditOnboarding<NeedsEnabled> {
pub fn enabled(self, enabled: bool) -> EditOnboarding<NeedsMode> {
impl<'a> EditOnboarding<'a, NeedsEnabled> {
pub fn enabled(self, enabled: bool) -> EditOnboarding<'a, NeedsMode> {
EditOnboarding {
prompts: self.prompts,
default_channel_ids: self.default_channel_ids,
enabled,
mode: self.mode,
audit_log_reason: self.audit_log_reason,

_stage: NeedsMode,
}
}
}

impl EditOnboarding<NeedsMode> {
pub fn mode(self, mode: OnboardingMode) -> EditOnboarding<Ready> {
impl<'a> EditOnboarding<'a, NeedsMode> {
pub fn mode(self, mode: OnboardingMode) -> EditOnboarding<'a, Ready> {
EditOnboarding {
prompts: self.prompts,
default_channel_ids: self.default_channel_ids,
enabled: self.enabled,
mode,
audit_log_reason: self.audit_log_reason,

_stage: Ready,
}
}
}

impl<'a, Stage: Sealed> EditOnboarding<'a, Stage> {
pub fn emoji(mut self, audit_log_reason: &'a str) -> Self {
self.audit_log_reason = Some(audit_log_reason);
self
}

Check failure on line 150 in src/builder/edit_onboarding/mod.rs

View workflow job for this annotation

GitHub Actions / Format

Diff in /home/runner/work/serenity/serenity/src/builder/edit_onboarding/mod.rs
}


#[cfg(feature = "http")]
#[async_trait::async_trait]
impl<'a> Builder for EditOnboarding<'a, Ready> {
type Context<'ctx> = GuildId;
type Built = Onboarding;

async fn execute(
mut self,
cache_http: impl CacheHttp,
ctx: Self::Context<'_>,

Check failure on line 163 in src/builder/edit_onboarding/mod.rs

View workflow job for this annotation

GitHub Actions / Format

Diff in /home/runner/work/serenity/serenity/src/builder/edit_onboarding/mod.rs
) -> Result<Self::Built> {
#[cfg(feature = "cache")]
crate::utils::user_has_guild_perms(&cache_http, ctx, Permissions::MANAGE_GUILD | Permissions::MANAGE_ROLES)?;

cache_http.http().set_guild_onboarding(ctx, &self, self.audit_log_reason).await
}
}
42 changes: 38 additions & 4 deletions src/builder/edit_onboarding/prompt_option_structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,18 @@ mod sealed {

use sealed::*;

#[derive(serde::Serialize, Clone, Debug)]
#[derive(Clone, Debug)]
#[must_use = "Builders do nothing unless built"]
pub struct CreatePromptOption<Stage: Sealed> {
channel_ids: Vec<ChannelId>,
role_ids: Vec<RoleId>,
emoji: Option<ReactionType>,
title: String,
description: Option<String>,

#[serde(skip)]
emoji: Option<ReactionType>,
_stage: Stage,

Check failure on line 33 in src/builder/edit_onboarding/prompt_option_structure.rs

View workflow job for this annotation

GitHub Actions / Format

Diff in /home/runner/work/serenity/serenity/src/builder/edit_onboarding/prompt_option_structure.rs
}


impl Default for CreatePromptOption<NeedsChannels> {
/// See the documentation of [`Self::new`].
fn default() -> Self {
Expand Down Expand Up @@ -109,3 +108,38 @@ impl<Stage: Sealed> CreatePromptOption<Stage> {
self
}

Check failure on line 109 in src/builder/edit_onboarding/prompt_option_structure.rs

View workflow job for this annotation

GitHub Actions / Format

Diff in /home/runner/work/serenity/serenity/src/builder/edit_onboarding/prompt_option_structure.rs
}

use serde::ser::{Serialize, Serializer, SerializeStruct};

impl<Stage: Sealed> Serialize for CreatePromptOption<Stage> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("CreatePromptOption", 4)?;

state.serialize_field("channel_ids", &self.channel_ids)?;
state.serialize_field("role_ids", &self.role_ids)?;
state.serialize_field("title", &self.title)?;
state.serialize_field("description", &self.description)?;

if let Some(ref emoji) = self.emoji {
match emoji {
ReactionType::Custom {
animated,
id,
name,
} => {
state.serialize_field("emoji_animated", animated)?;

Check failure on line 133 in src/builder/edit_onboarding/prompt_option_structure.rs

View workflow job for this annotation

GitHub Actions / Format

Diff in /home/runner/work/serenity/serenity/src/builder/edit_onboarding/prompt_option_structure.rs
state.serialize_field("emoji_id", id)?;
state.serialize_field("emoji_name", name)?;
}
ReactionType::Unicode(name) => {
state.serialize_field("emoji_name", name)?;
}
}
}

state.end()
}
}
8 changes: 8 additions & 0 deletions src/builder/edit_onboarding/prompt_structure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use crate::all::OnboardingPromptType;
#[derive(serde::Serialize, Clone, Debug)]
#[must_use = "Builders do nothing unless built"]
pub struct CreateOnboardingPrompt<Stage: Sealed> {
id: u64,
prompt_type: OnboardingPromptType,
options: Vec<CreatePromptOption<prompt_option_structure::Ready>>,
title: String,
Expand All @@ -52,6 +53,7 @@ impl Default for CreateOnboardingPrompt<NeedsPromptType> {
// Producing dummy values is okay as we must transition through all `Stage`s before firing,
// which fills in the values with real values.
Self {
id: 0,
prompt_type: OnboardingPromptType::Dropdown,
options: Vec::new(),
title: String::new(),
Expand All @@ -74,6 +76,7 @@ impl CreateOnboardingPrompt<NeedsPromptType> {
prompt_type: OnboardingPromptType,
) -> CreateOnboardingPrompt<NeedsPromptOptions> {
CreateOnboardingPrompt {
id: self.id,
prompt_type,
options: self.options,
title: self.title,
Expand All @@ -92,6 +95,7 @@ impl CreateOnboardingPrompt<NeedsPromptOptions> {
options: Vec<CreatePromptOption<prompt_option_structure::Ready>>,
) -> CreateOnboardingPrompt<NeedsTitle> {
CreateOnboardingPrompt {
id: self.id,
prompt_type: self.prompt_type,
options,
title: self.title,
Expand All @@ -107,6 +111,7 @@ impl CreateOnboardingPrompt<NeedsPromptOptions> {
impl CreateOnboardingPrompt<NeedsTitle> {
pub fn title(self, title: impl Into<String>) -> CreateOnboardingPrompt<NeedsSingleSelect> {
CreateOnboardingPrompt {
id: self.id,
prompt_type: self.prompt_type,
options: self.options,
title: title.into(),
Expand All @@ -122,6 +127,7 @@ impl CreateOnboardingPrompt<NeedsTitle> {
impl CreateOnboardingPrompt<NeedsSingleSelect> {
pub fn single_select(self, single_select: bool) -> CreateOnboardingPrompt<NeedsRequired> {
CreateOnboardingPrompt {
id: self.id,
prompt_type: self.prompt_type,
options: self.options,
title: self.title,
Expand All @@ -137,6 +143,7 @@ impl CreateOnboardingPrompt<NeedsSingleSelect> {
impl CreateOnboardingPrompt<NeedsRequired> {
pub fn required(self, required: bool) -> CreateOnboardingPrompt<NeedsInOnboarding> {
CreateOnboardingPrompt {
id: self.id,
prompt_type: self.prompt_type,
options: self.options,
title: self.title,
Expand All @@ -152,6 +159,7 @@ impl CreateOnboardingPrompt<NeedsRequired> {
impl CreateOnboardingPrompt<NeedsInOnboarding> {
pub fn in_onboarding(self, in_onboarding: bool) -> CreateOnboardingPrompt<Ready> {
CreateOnboardingPrompt {
id: self.id,
prompt_type: self.prompt_type,
options: self.options,
title: self.title,
Expand Down
17 changes: 17 additions & 0 deletions src/http/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4686,6 +4686,23 @@ impl Http {
.await
}

Check failure on line 4687 in src/http/client.rs

View workflow job for this annotation

GitHub Actions / Format

Diff in /home/runner/work/serenity/serenity/src/http/client.rs

/// Sets the onboarding configuration for the guild.
pub async fn set_guild_onboarding(&self, guild_id: GuildId, map: &impl serde::Serialize, audit_log_reason: Option<&str>) -> Result<Onboarding> {
let body = to_vec(map)?;

self.fire(Request {
body: Some(body),
multipart: None,
headers: audit_log_reason.map(reason_into_header),
method: LightMethod::Put,
route: Route::GuildOnboarding {
guild_id,
},
params: None,
})
.await
}

/// Fires off a request, deserializing the response reader via the given type bound.
///
/// If you don't need to deserialize the response and want the response instance itself, use
Expand Down
46 changes: 45 additions & 1 deletion src/model/guild/onboarding.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::all::ReactionType;

Check failure on line 1 in src/model/guild/onboarding.rs

View workflow job for this annotation

GitHub Actions / Format

Diff in /home/runner/work/serenity/serenity/src/model/guild/onboarding.rs
use crate::model::id::{ChannelId, GenericId, GuildId, RoleId};
use serde::{Deserialize, Deserializer};
use crate::model::id::{ChannelId, GenericId, GuildId, RoleId, EmojiId};

#[derive(Debug, Clone, Deserialize, Serialize)]
#[non_exhaustive]
Expand Down Expand Up @@ -41,6 +42,7 @@ pub struct OnboardingPromptOption {
pub id: GenericId,
pub channel_ids: Vec<ChannelId>,
pub role_ids: Vec<RoleId>,
#[serde(default, deserialize_with = "onboarding_reaction")]
pub emoji: Option<ReactionType>,
pub title: String,
pub description: Option<String>,
Expand All @@ -57,3 +59,45 @@ enum_number! {
_ => Unknown(u8),
}
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum TestType {
/// A reaction with a [`Guild`]s custom [`Emoji`], which is unique to the guild.
Custom {
/// Whether the emoji is animated.
animated: bool,
/// The Id of the custom [`Emoji`].
id: EmojiId,
/// The name of the custom emoji. This is primarily used for decoration and distinguishing
/// the emoji client-side.
name: Option<String>,
},
/// A reaction with a twemoji.
Unicode(String),
}

/// This exists to handle the weird case where discord decides to send every field as null
/// instead of sending the emoji as null itself.
fn onboarding_reaction<'de, D>(deserializer: D) -> Result<Option<ReactionType>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct PartialEmoji {
#[serde(default)]
animated: bool,
id: Option<EmojiId>,
name: Option<String>,
}
let emoji = PartialEmoji::deserialize(deserializer)?;
Ok(match (emoji.id, emoji.name) {
(Some(id), name) => Some(ReactionType::Custom {
animated: emoji.animated,
id,
name,
}),
(None, Some(name)) => Some(ReactionType::Unicode(name)),
(None, None) => return Ok(None),

Check failure on line 101 in src/model/guild/onboarding.rs

View workflow job for this annotation

GitHub Actions / Format

Diff in /home/runner/work/serenity/serenity/src/model/guild/onboarding.rs
})
}

0 comments on commit 64dc636

Please sign in to comment.