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

Validation #15

Merged
merged 8 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ pub const AUTOSTAR_COOLDOWN: (u32, Duration) = (5, Duration::from_secs(20));

// Common Validation
pub const MAX_NAME_LENGTH: u32 = 32;
pub const MIN_NAME_LENGTH: u32 = 3;

// AutoStar Validation
pub const MAX_MAX_CHARS: u16 = 5_000;
pub const MAX_MIN_CHARS: u16 = 5_000;
pub const MAX_MAX_CHARS: i16 = 5_000;
pub const MAX_MIN_CHARS: i16 = 5_000;

// Starboard Validation
pub const MIN_REQUIRED: i16 = -1;
pub const MAX_REQUIRED: u16 = 500;
pub const MAX_REQUIRED: i16 = 500;
pub const MIN_REQUIRED_REMOVE: i16 = -500;
pub const MAX_REQUIRED_REMOVE: i16 = 490;
pub const MIN_XP_MULTIPLIER: f32 = -10.0;
pub const MAX_XP_MULTIPLIER: f32 = 10.0;
pub const MAX_COOLDOWN_CAPACITY: i16 = 3600; // seconds in an hour
pub const MAX_COOLDOWN_PERIOD: i16 = MAX_COOLDOWN_CAPACITY;
7 changes: 7 additions & 0 deletions src/core/emoji.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ impl EmojiCommon for SimpleEmoji {
bot: &StarboardBot,
guild_id: Id<GuildMarker>,
) -> Option<Self> {
// Get rid of the Variation-Selector-16 codepoint that is sometimes present in user
// input. https://emojipedia.org/variation-selector-16/
let input = input
.strip_suffix("\u{fe0f}")
.map(|s| s.to_string())
.unwrap_or(input);

if emojis::get(&input).is_some() {
Some(Self {
is_custom: false,
Expand Down
52 changes: 50 additions & 2 deletions src/database/models/autostar_channel.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use sqlx::FromRow;

use crate::database::helpers::{
query::build_update::build_update, settings::autostar::call_with_autostar_settings,
use crate::{
constants,
database::helpers::{
query::build_update::build_update, settings::autostar::call_with_autostar_settings,
},
};

#[derive(Debug, FromRow)]
Expand Down Expand Up @@ -134,4 +137,49 @@ impl AutoStarChannel {
.fetch_optional(pool)
.await
}

// validation
pub fn set_min_chars(&mut self, val: i16) -> Result<(), String> {
if let Some(max_chars) = self.max_chars {
if val > max_chars {
return Err("`min-chars` cannot be greater than `max-chars`.".to_string());
}
}

if val > constants::MAX_MIN_CHARS {
Err(format!(
"`min-chars` cannot be greater than {}.",
constants::MAX_MIN_CHARS
))
} else if val < 0 {
Err("`min-chars` cannot be less than 0.".to_string())
} else {
self.min_chars = val;
Ok(())
}
}

pub fn set_max_chars(&mut self, val: Option<i16>) -> Result<(), String> {
match val {
None => {
self.max_chars = None;
Ok(())
}
Some(val) => {
if val < self.min_chars {
Err("`max-chars` cannot be less than `min-chars.".to_string())
} else if val < 0 {
Err("`max-chars` cannot be less than 0.".to_string())
} else if val > constants::MAX_MAX_CHARS {
Err(format!(
"`max-chars` cannot be greater than {}.",
constants::MAX_MAX_CHARS
))
} else {
self.max_chars = Some(val);
Ok(())
}
}
}
}
}
1 change: 1 addition & 0 deletions src/database/validation/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod color;
pub mod name;
pub mod starboard_settings;
pub mod time_delta;
96 changes: 96 additions & 0 deletions src/database/validation/starboard_settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//! Validation for certain starboard settings that are shared between
//! starboards and overrides, but not elsewhere and thus don't deserve
//! their own file.

use std::collections::HashSet;

use crate::constants;

pub fn validate_required(val: i16, required_remove: i16) -> Result<(), String> {
if val <= required_remove {
Err("`required` must be greater than `required-remove`.".to_string())
} else if val < constants::MIN_REQUIRED {
Err(format!(
"`required` cannot be less than {}.",
constants::MIN_REQUIRED
))
} else if val > constants::MAX_REQUIRED {
Err(format!(
"`required` cannot be greater than {}.",
constants::MAX_REQUIRED
))
} else {
Ok(())
}
}

pub fn validate_required_remove(val: i16, required: i16) -> Result<(), String> {
if val >= required {
Err("`required-remove` must be less than `required`.".to_string())
} else if val < constants::MIN_REQUIRED_REMOVE {
Err(format!(
"`required-remove` cannot be less than {}.",
constants::MIN_REQUIRED_REMOVE
))
} else if val > constants::MAX_REQUIRED_REMOVE {
Err(format!(
"`required-remove` cannot be greater than {}.",
constants::MAX_REQUIRED_REMOVE
))
} else {
Ok(())
}
}

pub fn validate_xp_multiplier(val: f32) -> Result<(), String> {
if val > constants::MAX_XP_MULTIPLIER {
Err(format!(
"`xp-multiplier` cannot be greater than {}.",
constants::MAX_XP_MULTIPLIER
))
} else if val < constants::MIN_XP_MULTIPLIER {
Err(format!(
"`xp-multiplier` cannot be less than {}.",
constants::MIN_XP_MULTIPLIER
))
} else {
Ok(())
}
}

pub fn validate_cooldown(capacity: i16, period: i16) -> Result<(), String> {
if capacity <= 0 || period <= 0 {
Err("The capacity and period for the cooldown must be greater than 0.".to_string())
} else if capacity > constants::MAX_COOLDOWN_CAPACITY {
Err(format!(
"The capacity cannot be greater than {}.",
constants::MAX_COOLDOWN_CAPACITY
))
} else if period > constants::MAX_COOLDOWN_PERIOD {
Err(format!(
"The period cannot be greater than {}.",
constants::MAX_COOLDOWN_PERIOD
))
} else {
Ok(())
}
}

pub fn validate_vote_emojis(
upvote: &Vec<String>,
downvote: &Vec<String>,
) -> Result<(), &'static str> {
let unique_upvote: HashSet<_> = upvote.iter().collect();
let unique_downvote: HashSet<_> = downvote.iter().collect();

if unique_upvote
.intersection(&unique_downvote)
.collect::<Vec<_>>()
.len()
> 0
{
Err("Upvote emojis and downvote emojis cannot share the same emojis.")
} else {
Ok(())
}
}
12 changes: 10 additions & 2 deletions src/interactions/commands/chat/autostar/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,22 @@ impl EditAutoStar {
.into_stored();
}
if let Some(val) = self.min_chars {
asc.min_chars = val.try_into().unwrap();
let min_chars = val.try_into().unwrap();
if let Err(why) = asc.set_min_chars(min_chars) {
ctx.respond_str(&why, true).await?;
return Ok(());
}
}
if let Some(val) = self.max_chars {
asc.max_chars = if val == -1 {
let max_chars = if val == -1 {
None
} else {
Some(val.try_into().unwrap())
};
if let Err(why) = asc.set_max_chars(max_chars) {
ctx.respond_str(&why, true).await?;
return Ok(());
}
}
if let Some(val) = self.require_image {
asc.require_image = val;
Expand Down
17 changes: 12 additions & 5 deletions src/interactions/commands/chat/starboard/edit/behavior.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use lazy_static::lazy_static;
use twilight_interactions::command::{CommandModel, CreateCommand};

use crate::{
database::Starboard, get_guild_id, interactions::commands::context::CommandCtx, unwrap_id,
database::{validation, Starboard},
get_guild_id,
interactions::commands::context::CommandCtx,
unwrap_id,
};

#[derive(CommandModel, CreateCommand)]
Expand Down Expand Up @@ -32,7 +35,7 @@ pub struct EditBehavior {
/// If true, prevents /random and /moststarred from pulling from this starboard.
private: Option<bool>,
/// How much XP each upvote on this starboard counts for.
#[command(rename = "xp-multiplier")]
#[command(rename = "xp-multiplier", min_value = 10, max_value = 10)]
xp_multiplier: Option<f64>,
/// Whether to enable the per-user vote cooldown.
#[command(rename = "cooldown-enabled")]
Expand Down Expand Up @@ -76,14 +79,17 @@ impl EditBehavior {
starboard.settings.private = val;
}
if let Some(val) = self.xp_multiplier {
// TODO: validation
starboard.settings.xp_multiplier = val.to_string().parse().unwrap();
let val = val.to_string().parse().unwrap();
if let Err(why) = validation::starboard_settings::validate_xp_multiplier(val) {
ctx.respond_str(&why, true).await?;
return Ok(());
}
starboard.settings.xp_multiplier = val;
}
if let Some(val) = self.cooldown_enabled {
starboard.settings.cooldown_enabled = val;
}
if let Some(val) = self.cooldown {
// TODO: validation
let (capacity, period) = match parse_cooldown(&val) {
Err(why) => {
ctx.respond_str(&why, true).await?;
Expand Down Expand Up @@ -134,5 +140,6 @@ fn parse_cooldown(inp: &str) -> Result<(i16, i16), String> {
Ok(period) => period,
};

validation::starboard_settings::validate_cooldown(capacity, period)?;
Ok((capacity, period))
}
55 changes: 44 additions & 11 deletions src/interactions/commands/chat/starboard/edit/requirements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use twilight_interactions::command::{CommandModel, CreateCommand};

use crate::{
core::emoji::{EmojiCommon, SimpleEmoji},
database::{validation::time_delta::parse_time_delta, Starboard},
database::{
validation::{self, time_delta::parse_time_delta},
Starboard,
},
get_guild_id,
interactions::commands::context::CommandCtx,
unwrap_id,
Expand Down Expand Up @@ -67,22 +70,52 @@ impl EditRequirements {
};

if let Some(val) = self.required {
starboard.settings.required = val.try_into().unwrap();
let val = val.try_into().unwrap();
if let Err(why) = validation::starboard_settings::validate_required(
val,
starboard.settings.required_remove,
) {
ctx.respond_str(&why, true).await?;
return Ok(());
}
starboard.settings.required = val;
}
if let Some(val) = self.required_remove {
starboard.settings.required_remove = val.try_into().unwrap();
let val = val.try_into().unwrap();
if let Err(why) = validation::starboard_settings::validate_required_remove(
val,
starboard.settings.required,
) {
ctx.respond_str(&why, true).await?;
return Ok(());
}
starboard.settings.required_remove = val;
}
if let Some(val) = self.upvote_emojis {
starboard.settings.upvote_emojis =
Vec::<SimpleEmoji>::from_user_input(val, &ctx.bot, guild_id)
.await
.into_stored();
let emojis = Vec::<SimpleEmoji>::from_user_input(val, &ctx.bot, guild_id)
.await
.into_stored();
if let Err(why) = validation::starboard_settings::validate_vote_emojis(
&emojis,
&starboard.settings.downvote_emojis,
) {
ctx.respond_str(why, true).await?;
return Ok(());
}
starboard.settings.upvote_emojis = emojis;
}
if let Some(val) = self.downvote_emojis {
starboard.settings.downvote_emojis =
Vec::<SimpleEmoji>::from_user_input(val, &ctx.bot, guild_id)
.await
.into_stored();
let emojis = Vec::<SimpleEmoji>::from_user_input(val, &ctx.bot, guild_id)
.await
.into_stored();
if let Err(why) = validation::starboard_settings::validate_vote_emojis(
&starboard.settings.upvote_emojis,
&emojis,
) {
ctx.respond_str(why, true).await?;
return Ok(());
}
starboard.settings.downvote_emojis = emojis;
}
if let Some(val) = self.self_vote {
starboard.settings.self_vote = val;
Expand Down