Rustic cleanups, dedups and making the code less hard to read in general (#251)

* typos :help_me:

* (part 1/?) massive cleanup to make the code more Rust-ic and cut down heap allocations.

* (part 2/?) massive cleanup to make the code more Rust-ic and cut down heap allocations.

* (part 3/?) cut down some pretty major heap allocations here - more Bytes and BytesMuts, less Vec<u8>s

also I don't really understand why you need to `to_vec` when you don't really use it again afterwards

* (part 4/?) deduplicate error handling in backblaze logic

* (part 5/?) fixes, cleanups, refactors, and reformatting

* (part 6/?) cleanups and refactors

* remove loads of `as_str` in types that already are `Display`

* Revert "remove loads of `as_str` in types that already are `Display`"

This reverts commit 4f974310cfb167ceba03001d81388db4f0fbb509.

* reformat and move routes util to the util module

* use streams

* Run prepare + formatting issues

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
Leo Chen
2021-10-12 11:26:59 +08:00
committed by GitHub
parent 0010119440
commit 13187de97d
53 changed files with 997 additions and 1129 deletions

View File

@@ -16,13 +16,9 @@ pub use super::users::UserId;
/// This method panics if `n` is 0 or greater than 11, since a `u64`
/// can only represent up to 11 character base62 strings
#[allow(dead_code)]
#[inline]
pub fn random_base62(n: usize) -> u64 {
use rand::Rng;
assert!(n > 0 && n <= 11);
let mut rng = rand::thread_rng();
// gen_range is [low, high): max value is `MULTIPLES[n] - 1`,
// which is n characters long when encoded
rng.gen_range(MULTIPLES[n - 1], MULTIPLES[n])
random_base62_rng(&mut rand::thread_rng(), n)
}
/// Generates a random 64 bit integer that is exactly `n` characters
@@ -35,6 +31,8 @@ pub fn random_base62(n: usize) -> u64 {
pub fn random_base62_rng<R: rand::RngCore>(rng: &mut R, n: usize) -> u64 {
use rand::Rng;
assert!(n > 0 && n <= 11);
// gen_range is [low, high): max value is `MULTIPLES[n] - 1`,
// which is n characters long when encoded
rng.gen_range(MULTIPLES[n - 1], MULTIPLES[n])
}
@@ -50,7 +48,7 @@ const MULTIPLES: [u64; 12] = [
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62 * 62,
std::u64::MAX,
u64::MAX,
];
/// An ID encoded as base62 for use in the API.
@@ -63,7 +61,7 @@ pub struct Base62Id(pub u64);
/// An error decoding a number from base62.
#[derive(Error, Debug)]
pub enum DecodingError {
/// Encountered a non base62 character in base62 string
/// Encountered a non-base62 character in a base62 string
#[error("Invalid character {0:?} in base62 encoding")]
InvalidBase62(char),
/// Encountered integer overflow when decoding a base62 id.
@@ -154,13 +152,8 @@ pub mod base62_impl {
}
}
const BASE62_CHARS: [u8; 62] = [
b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E',
b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T',
b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i',
b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x',
b'y', b'z',
];
const BASE62_CHARS: [u8; 62] =
*b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
pub fn to_base62(mut num: u64) -> String {
let length = (num as f64).log(62.0).ceil() as usize;
@@ -189,7 +182,7 @@ pub mod base62_impl {
return Err(DecodingError::InvalidBase62(c));
}
// We don't want this panicing or wrapping on integer overflow
// We don't want this panicking or wrapping on integer overflow
if let Some(n) = num.checked_mul(62).and_then(|n| n.checked_add(next_digit)) {
num = n;
} else {

View File

@@ -4,4 +4,4 @@ pub mod notifications;
pub mod projects;
pub mod reports;
pub mod teams;
pub mod users;
pub mod users;

View File

@@ -22,9 +22,37 @@ pub struct Notification {
pub actions: Vec<NotificationAction>,
}
use crate::database::models::notification_item::Notification as DBNotification;
use crate::database::models::notification_item::NotificationAction as DBNotificationAction;
impl From<DBNotification> for Notification {
fn from(notif: DBNotification) -> Self {
Self {
id: notif.id.into(),
user_id: notif.user_id.into(),
type_: notif.notification_type,
title: notif.title,
text: notif.text,
link: notif.link,
read: notif.read,
created: notif.created,
actions: notif.actions.into_iter().map(Into::into).collect(),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct NotificationAction {
pub title: String,
/// The route to call when this notification action is called. Formatted HTTP Method, route
pub action_route: (String, String),
}
impl From<DBNotificationAction> for NotificationAction {
fn from(act: DBNotificationAction) -> Self {
Self {
title: act.title,
action_route: (act.action_route_method, act.action_route),
}
}
}

View File

@@ -1,6 +1,8 @@
use super::ids::Base62Id;
use super::teams::TeamId;
use super::users::UserId;
use crate::database::models::project_item::QueryProject;
use crate::database::models::version_item::QueryVersion;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use validator::Validate;
@@ -80,6 +82,70 @@ pub struct Project {
pub gallery: Vec<GalleryItem>,
}
impl From<QueryProject> for Project {
fn from(data: QueryProject) -> Self {
let m = data.inner;
Self {
id: m.id.into(),
slug: m.slug,
project_type: data.project_type,
team: m.team_id.into(),
title: m.title,
description: m.description,
body: m.body,
body_url: m.body_url,
published: m.published,
updated: m.updated,
status: data.status,
moderator_message: if let Some(message) = m.moderation_message {
Some(ModeratorMessage {
message,
body: m.moderation_message_body,
})
} else {
None
},
license: License {
id: data.license_id,
name: data.license_name,
url: m.license_url,
},
client_side: data.client_side,
server_side: data.server_side,
downloads: m.downloads as u32,
followers: m.follows as u32,
categories: data.categories,
versions: data.versions.into_iter().map(|v| v.into()).collect(),
icon_url: m.icon_url,
issues_url: m.issues_url,
source_url: m.source_url,
wiki_url: m.wiki_url,
discord_url: m.discord_url,
donation_urls: Some(
data.donation_urls
.into_iter()
.map(|d| DonationLink {
id: d.platform_short,
platform: d.platform_name,
url: d.url,
})
.collect(),
),
gallery: data
.gallery_items
.into_iter()
.map(|x| GalleryItem {
url: x.image_url,
featured: x.featured,
title: x.title,
description: x.description,
created: x.created,
})
.collect(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GalleryItem {
pub url: String,
@@ -146,7 +212,7 @@ pub struct DonationLink {
pub url: String,
}
/// A status decides the visbility of a project in search, URLs, and the whole site itself.
/// A status decides the visibility of a project in search, URLs, and the whole site itself.
/// Approved - Project is displayed on search, and accessible by URL
/// Rejected - Project is not displayed on search, and not accessible by URL (Temporary state, project can reapply)
/// Draft - Project is not displayed on search, and not accessible by URL
@@ -248,6 +314,61 @@ pub struct Version {
pub loaders: Vec<Loader>,
}
impl From<QueryVersion> for Version {
fn from(data: QueryVersion) -> Version {
Version {
id: data.id.into(),
project_id: data.project_id.into(),
author_id: data.author_id.into(),
featured: data.featured,
name: data.name,
version_number: data.version_number,
changelog: data.changelog,
changelog_url: data.changelog_url,
date_published: data.date_published,
downloads: data.downloads as u32,
version_type: match data.version_type.as_str() {
"release" => VersionType::Release,
"beta" => VersionType::Beta,
"alpha" => VersionType::Alpha,
_ => VersionType::Release,
},
files: data
.files
.into_iter()
.map(|f| {
VersionFile {
url: f.url,
filename: f.filename,
// FIXME: Hashes are currently stored as an ascii byte slice instead
// of as an actual byte array in the database
hashes: f
.hashes
.into_iter()
.map(|(k, v)| Some((k, String::from_utf8(v).ok()?)))
.collect::<Option<_>>()
.unwrap_or_default(),
primary: f.primary,
}
})
.collect(),
dependencies: data
.dependencies
.into_iter()
.map(|d| Dependency {
version_id: d.version_id.map(|i| VersionId(i.0 as u64)),
project_id: d.project_id.map(|i| ProjectId(i.0 as u64)),
dependency_type: DependencyType::from_str(d.dependency_type.as_str()),
})
.collect(),
game_versions: data.game_versions.into_iter().map(GameVersion).collect(),
loaders: data.loaders.into_iter().map(Loader).collect(),
}
}
}
/// A single project file, with a url for the file and the file's hash
#[derive(Serialize, Deserialize)]
pub struct VersionFile {
@@ -284,11 +405,7 @@ pub enum VersionType {
impl std::fmt::Display for VersionType {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
VersionType::Release => write!(fmt, "release"),
VersionType::Beta => write!(fmt, "beta"),
VersionType::Alpha => write!(fmt, "alpha"),
}
fmt.write_str(self.as_str())
}
}
@@ -313,11 +430,7 @@ pub enum DependencyType {
impl std::fmt::Display for DependencyType {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
DependencyType::Required => write!(fmt, "required"),
DependencyType::Optional => write!(fmt, "optional"),
DependencyType::Incompatible => write!(fmt, "incompatible"),
}
fmt.write_str(self.as_str())
}
}

View File

@@ -1,4 +1,5 @@
use super::ids::Base62Id;
use crate::database::models::team_item::QueryTeamMember;
use crate::models::users::User;
use serde::{Deserialize, Serialize};
@@ -57,3 +58,19 @@ pub struct TeamMember {
/// Whether the user has joined the team or is just invited to it
pub accepted: bool,
}
impl TeamMember {
pub fn from(data: QueryTeamMember, override_permissions: bool) -> Self {
Self {
team_id: data.team_id.into(),
user: data.user.into(),
role: data.role,
permissions: if override_permissions {
None
} else {
Some(data.permissions)
},
accepted: data.accepted,
}
}
}

View File

@@ -21,6 +21,23 @@ pub struct User {
pub role: Role,
}
use crate::database::models::user_item::User as DBUser;
impl From<DBUser> for User {
fn from(data: DBUser) -> Self {
Self {
id: data.id.into(),
github_id: data.github_id.map(|i| i as u64),
username: data.username,
name: data.name,
email: None,
avatar_url: data.avatar_url,
bio: data.bio,
created: data.created,
role: Role::from_string(&*data.role),
}
}
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "lowercase")]
pub enum Role {
@@ -31,11 +48,7 @@ pub enum Role {
impl std::fmt::Display for Role {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Role::Developer => write!(fmt, "developer"),
Role::Moderator => write!(fmt, "moderator"),
Role::Admin => write!(fmt, "admin"),
}
fmt.write_str(self.as_str())
}
}
@@ -48,6 +61,14 @@ impl Role {
}
}
pub fn as_str(&self) -> &'static str {
match self {
Role::Developer => "developer",
Role::Moderator => "moderator",
Role::Admin => "admin",
}
}
pub fn is_mod(&self) -> bool {
match self {
Role::Developer => false,