Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(model, cache): Add support for application editing and new application fields #2284

Merged
3 changes: 3 additions & 0 deletions twilight-http-ratelimiting/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ pub enum Path {
ApplicationGuildCommand(u64),
/// Operating on a specific command in a guild.
ApplicationGuildCommandId(u64),
/// Operating on current user application,
ApplicationsMe,
/// Operating on a channel.
ChannelsId(u64),
/// Operating on a channel's followers.
Expand Down Expand Up @@ -330,6 +332,7 @@ impl FromStr for Path {
let parts = s.split('/').skip(skip).collect::<Vec<&str>>();

Ok(match parts[..] {
["applications", "me"] => ApplicationsMe,
["applications", id, "commands"] => ApplicationCommand(parse_id(id)?),
["applications", id, "commands", _] => ApplicationCommandId(parse_id(id)?),
["applications", id, "guilds", _, "commands"]
Expand Down
9 changes: 8 additions & 1 deletion twilight-http/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ mod interaction;

pub use self::{builder::ClientBuilder, interaction::InteractionClient};

use crate::request::{guild::GetGuildOnboarding, GetCurrentAuthorizationInformation};
use crate::request::{
guild::GetGuildOnboarding, GetCurrentAuthorizationInformation, UpdateCurrentUserApplication,
};
#[allow(deprecated)]
use crate::{
client::connector::Connector,
Expand Down Expand Up @@ -692,6 +694,11 @@ impl Client {
GetUserApplicationInfo::new(self)
}

/// Update the current user's application.
pub const fn update_current_user_application(&self) -> UpdateCurrentUserApplication<'_> {
UpdateCurrentUserApplication::new(self)
}

/// Update the current user.
///
/// All parameters are optional. If the username is changed, it may cause the discriminator to
Expand Down
2 changes: 2 additions & 0 deletions twilight-http/src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ mod get_user_application;
mod get_voice_regions;
mod multipart;
mod try_into_request;
mod update_user_application;

pub use self::{
audit_reason::AuditLogReason,
Expand All @@ -69,6 +70,7 @@ pub use self::{
get_voice_regions::GetVoiceRegions,
multipart::Form,
try_into_request::TryIntoRequest,
update_user_application::UpdateCurrentUserApplication,
};
pub use twilight_http_ratelimiting::request::Method;

Expand Down
2 changes: 2 additions & 0 deletions twilight-http/src/request/try_into_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ mod private {
CreateGuildFromTemplate, CreateTemplate, DeleteTemplate, GetTemplate, GetTemplates,
SyncTemplate, UpdateTemplate,
},
update_user_application::UpdateCurrentUserApplication,
user::{
CreatePrivateChannel, GetCurrentUser, GetCurrentUserConnections,
GetCurrentUserGuildMember, GetCurrentUserGuilds, GetUser, LeaveGuild,
Expand Down Expand Up @@ -267,6 +268,7 @@ mod private {
impl Sealed for UpdateWebhook<'_> {}
impl Sealed for UpdateWebhookMessage<'_> {}
impl Sealed for UpdateWebhookWithToken<'_> {}
impl Sealed for UpdateCurrentUserApplication<'_> {}
}

use super::base::Request;
Expand Down
151 changes: 151 additions & 0 deletions twilight-http/src/request/update_user_application.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use std::future::IntoFuture;

use serde::Serialize;
use twilight_model::oauth::{Application, ApplicationFlags, InstallParams};

use crate::{
client::Client,
error::Error,
request::{Nullable, Request, TryIntoRequest},
response::{Response, ResponseFuture},
routing::Route,
};

#[derive(Serialize)]
struct UpdateCurrentUserApplicationFields<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
cover_image: Option<Nullable<&'a str>>,
#[serde(skip_serializing_if = "Option::is_none")]
custom_install_url: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
flags: Option<ApplicationFlags>,
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<Nullable<&'a str>>,
#[serde(skip_serializing_if = "Option::is_none")]
install_params: Option<InstallParams>,
#[serde(skip_serializing_if = "Option::is_none")]
interactions_endpoint_url: Option<Nullable<&'a str>>,
#[serde(skip_serializing_if = "Option::is_none")]
role_connections_verification_url: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
tags: Option<Vec<&'a str>>,
}

#[must_use = "requests must be configured and executed"]
pub struct UpdateCurrentUserApplication<'a> {
fields: UpdateCurrentUserApplicationFields<'a>,
http: &'a Client,
}

impl<'a> UpdateCurrentUserApplication<'a> {
pub(crate) const fn new(http: &'a Client) -> Self {
Self {
fields: UpdateCurrentUserApplicationFields {
cover_image: None,
custom_install_url: None,
description: None,
flags: None,
icon: None,
install_params: None,
interactions_endpoint_url: None,
role_connections_verification_url: None,
tags: None,
},
http,
}
}

/// Set the cover image of the application.
pub const fn cover_image(mut self, cover_image: &'a str) -> Self {
self.fields.cover_image = Some(Nullable(Some(cover_image)));

self
}

/// Set the custom install URL of the application.
pub const fn custom_install_url(mut self, custom_install_url: &'a str) -> Self {
self.fields.custom_install_url = Some(custom_install_url);

self
}

/// Set the description of the application.
pub const fn description(mut self, description: &'a str) -> Self {
self.fields.description = Some(description);

self
}

/// Set the flags of the application.
pub const fn flags(mut self, flags: ApplicationFlags) -> Self {
self.fields.flags = Some(flags);

self
}

/// Set the icon of the application.
pub const fn icon(mut self, icon: &'a str) -> Self {
self.fields.icon = Some(Nullable(Some(icon)));

self
}

/// Set the install params of the application.
#[allow(clippy::missing_const_for_fn)]
pub fn install_params(mut self, install_params: InstallParams) -> Self {
self.fields.install_params = Some(install_params);

self
}

/// Set the interactions endpoint URL of the application.
pub const fn interactions_endpoint_url(mut self, interactions_endpoint_url: &'a str) -> Self {
self.fields.interactions_endpoint_url = Some(Nullable(Some(interactions_endpoint_url)));

self
}

/// Set the role connections verification URL of the application.
pub const fn role_connections_verification_url(
mut self,
role_connections_verification_url: &'a str,
) -> Self {
self.fields.role_connections_verification_url = Some(role_connections_verification_url);

self
}

/// Set the tags of the application.
pub fn tags(mut self, tags: Vec<&'a str>) -> Self {
self.fields.tags = Some(tags);

self
}
}

impl IntoFuture for UpdateCurrentUserApplication<'_> {
type Output = Result<Response<Application>, Error>;

type IntoFuture = ResponseFuture<Application>;

fn into_future(self) -> Self::IntoFuture {
let http = self.http;

match self.try_into_request() {
Ok(request) => http.request(request),
Err(source) => ResponseFuture::error(source),
}
}
}

impl TryIntoRequest for UpdateCurrentUserApplication<'_> {
fn try_into_request(self) -> Result<Request, Error> {
let mut request = Request::builder(&Route::UpdateCurrentUserApplication);

request = request.json(&self.fields)?;

Ok(request.build())
}
}
23 changes: 19 additions & 4 deletions twilight-http/src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use twilight_model::id::{marker::RoleMarker, Id};
#[non_exhaustive]
pub enum Route<'a> {
/// Route information to add a user to a guild.
AddGuildMember { guild_id: u64, user_id: u64 },
AddGuildMember {
guild_id: u64,
user_id: u64,
},
/// Route information to add a role to guild member.
AddMemberRole {
/// The ID of the guild.
Expand Down Expand Up @@ -1095,6 +1098,7 @@ pub enum Route<'a> {
token: &'a str,
webhook_id: u64,
},
UpdateCurrentUserApplication,
}

impl<'a> Route<'a> {
Expand Down Expand Up @@ -1245,6 +1249,7 @@ impl<'a> Route<'a> {
| Self::UpdateTemplate { .. }
| Self::UpdateUserVoiceState { .. }
| Self::UpdateWebhookMessage { .. }
| Self::UpdateCurrentUserApplication { .. }
| Self::UpdateWebhook { .. } => Method::Patch,
Self::CreateChannel { .. }
| Self::CreateGlobalCommand { .. }
Expand Down Expand Up @@ -1532,7 +1537,9 @@ impl<'a> Route<'a> {
Path::ApplicationGuildCommandId(application_id)
}
Self::GetCurrentAuthorizationInformation => Path::OauthMe,
Self::GetCurrentUserApplicationInfo => Path::OauthApplicationsMe,
Self::GetCurrentUserApplicationInfo | Self::UpdateCurrentUserApplication => {
Path::ApplicationsMe
}
Self::GetCurrentUser | Self::GetUser { .. } | Self::UpdateCurrentUser => Path::UsersId,
Self::GetCurrentUserGuildMember { .. } => Path::UsersIdGuildsIdMember,
Self::GetEmoji { guild_id, .. } | Self::UpdateEmoji { guild_id, .. } => {
Expand Down Expand Up @@ -2334,7 +2341,9 @@ impl Display for Route<'_> {
f.write_str("/permissions")
}
Route::GetCurrentAuthorizationInformation => f.write_str("oauth2/@me"),
Route::GetCurrentUserApplicationInfo => f.write_str("oauth2/applications/@me"),
Route::GetCurrentUserApplicationInfo | Route::UpdateCurrentUserApplication => {
f.write_str("applications/@me")
}
Route::GetCurrentUser | Route::UpdateCurrentUser => f.write_str("users/@me"),
Route::GetCurrentUserGuildMember { guild_id } => {
f.write_str("users/@me/guilds/")?;
Expand Down Expand Up @@ -3998,7 +4007,13 @@ mod tests {
#[test]
fn get_current_user_application_info() {
let route = Route::GetCurrentUserApplicationInfo;
assert_eq!(route.to_string(), "oauth2/applications/@me");
assert_eq!(route.to_string(), "applications/@me");
}

#[test]
fn update_current_user_application() {
let route = Route::UpdateCurrentUserApplication;
assert_eq!(route.to_string(), "applications/@me");
}

#[test]
Expand Down
20 changes: 19 additions & 1 deletion twilight-model/src/oauth/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Application {
/// Partial user object for the bot user associated with the app.
#[serde(skip_serializing_if = "Option::is_none")]
pub bot: Option<User>,
pub bot_public: bool,
pub bot_require_code_grant: bool,
/// Default rich presence invite cover image.
Expand All @@ -30,13 +33,19 @@ pub struct Application {
/// Settings for the application's default in-app authorization, if enabled.
#[serde(skip_serializing_if = "Option::is_none")]
pub install_params: Option<InstallParams>,
/// Interactions endpoint URL for the app.
#[serde(skip_serializing_if = "Option::is_none")]
pub interactions_endpoint_url: Option<String>,
/// Name of the application.
pub name: String,
pub owner: Option<User>,
pub primary_sku_id: Option<Id<OauthSkuMarker>>,
/// URL of the application's privacy policy.
#[serde(skip_serializing_if = "Option::is_none")]
pub privacy_policy_url: Option<String>,
/// Role connection verification URL for the app.
#[serde(skip_serializing_if = "Option::is_none")]
pub role_connections_verification_url: Option<String>,
#[serde(default)]
pub rpc_origins: Vec<String>,
pub slug: Option<String>,
Expand Down Expand Up @@ -96,6 +105,7 @@ mod tests {
#[test]
fn current_application_info() {
let value = Application {
bot: None,
bot_public: true,
bot_require_code_grant: false,
cover_image: Some(image_hash::COVER),
Expand All @@ -106,6 +116,7 @@ mod tests {
icon: Some(image_hash::ICON),
id: Id::new(2),
install_params: None,
interactions_endpoint_url: Some("https://interactions".into()),
name: "cool application".to_owned(),
owner: Some(User {
accent_color: None,
Expand All @@ -128,6 +139,7 @@ mod tests {
}),
primary_sku_id: Some(Id::new(4)),
privacy_policy_url: Some("https://privacypolicy".into()),
role_connections_verification_url: Some("https://roleconnections".into()),
rpc_origins: vec!["one".to_owned()],
slug: Some("app slug".to_owned()),
tags: Some(Vec::from([
Expand All @@ -152,7 +164,7 @@ mod tests {
&[
Token::Struct {
name: "Application",
len: 18,
len: 20,
},
Token::Str("bot_public"),
Token::Bool(true),
Expand All @@ -176,6 +188,9 @@ mod tests {
Token::Str("id"),
Token::NewtypeStruct { name: "Id" },
Token::Str("2"),
Token::Str("interactions_endpoint_url"),
Token::Some,
Token::Str("https://interactions"),
Token::Str("name"),
Token::Str("cool application"),
Token::Str("owner"),
Expand Down Expand Up @@ -212,6 +227,9 @@ mod tests {
Token::Str("privacy_policy_url"),
Token::Some,
Token::Str("https://privacypolicy"),
Token::Str("role_connections_verification_url"),
Token::Some,
Token::Str("https://roleconnections"),
Token::Str("rpc_origins"),
Token::Seq { len: Some(1) },
Token::Str("one"),
Expand Down
3 changes: 3 additions & 0 deletions twilight-model/src/oauth/current_authorization_information.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ mod tests {

let value = CurrentAuthorizationInformation {
application: Application {
bot: None,
bot_public: true,
bot_require_code_grant: true,
cover_image: None,
Expand All @@ -81,10 +82,12 @@ mod tests {
icon: Some(image_hash::ICON),
id: Id::new(100_000_000_000_000_000),
install_params: None,
interactions_endpoint_url: None,
name: NAME.to_owned(),
owner: None,
primary_sku_id: None,
privacy_policy_url: None,
role_connections_verification_url: None,
rpc_origins: Vec::new(),
slug: None,
tags: None,
Expand Down