refactor: labrinth ApiError and error reporting (#6981)

* refactor: labrinth `ApiError` and error reporting

* fix clippy

* fix ci
This commit is contained in:
aecsocket
2026-08-07 15:47:54 +00:00
committed by GitHub
parent d344cbcb7a
commit 3fa6905006
102 changed files with 6449 additions and 3791 deletions
+8 -2
View File
@@ -6,6 +6,7 @@ use crate::queue::session::AuthQueue;
use crate::routes::ApiError;
use crate::routes::v2_reroute;
use crate::routes::v3;
use crate::util::error::ApiContext as _;
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
use serde::{Deserialize, Serialize};
use xredis::RedisPool;
@@ -65,7 +66,11 @@ pub async fn notifications_get(
)
.await
.or_else(v2_reroute::flatten_404_error);
match v2_reroute::extract_ok_json::<Vec<Notification>>(resp?).await {
match v2_reroute::extract_ok_json::<Vec<Notification>>(
resp.wrap_api_err("extracting v2 response body")?,
)
.await
{
Ok(notifications) => {
let notifications: Vec<LegacyNotification> = notifications
.into_iter()
@@ -115,7 +120,8 @@ pub async fn notification_get(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
match v2_reroute::extract_ok_json::<Notification>(response).await {
Ok(notification) => {
let notification = LegacyNotification::from(notification);
+48 -16
View File
@@ -13,6 +13,8 @@ use crate::queue::session::AuthQueue;
use crate::routes::v3::projects::ProjectIds;
use crate::routes::{ApiError, v2_reroute, v3};
use crate::search::{SearchBackend, SearchRequest, SearchState};
use crate::util::error::ApiContext as _;
use crate::util::error::Context as _;
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -79,7 +81,8 @@ pub async fn project_search(
// While the backend for this has changed, it doesnt affect much
// in the API calls except that 'versions:x' is now 'game_versions:x'
let facets: Option<Vec<Vec<String>>> = if let Some(facets) = info.facets {
let facets = serde_json::from_str::<Vec<Vec<String>>>(&facets)?;
let facets = serde_json::from_str::<Vec<Vec<String>>>(&facets)
.wrap_request_err("deserializing JSON data")?;
Some(
facets
@@ -119,7 +122,10 @@ pub async fn project_search(
..info
};
let results = search_backend.search_for_project(&info, &redis).await?;
let results = search_backend
.search_for_project(&info, &redis)
.await
.wrap_api_err("searching projects")?;
let results = LegacySearchResults::from(results);
@@ -194,12 +200,17 @@ pub async fn random_projects_get(
)
.await
.or_else(v2_reroute::flatten_404_error)
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Project>>(response).await {
Ok(project) => {
let legacy_projects =
LegacyProject::from_many(project, &**pool, &redis).await?;
LegacyProject::from_many(project, &**pool, &redis)
.await
.wrap_internal_err(
"executing `LegacyProject::from_many`",
)?;
Ok(HttpResponse::Ok().json(legacy_projects))
}
Err(response) => Ok(response),
@@ -234,13 +245,18 @@ pub async fn projects_get(
)
.await
.or_else(v2_reroute::flatten_404_error)
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Project>>(response).await {
Ok(project) => {
let legacy_projects =
LegacyProject::from_many(project, &**pool, &redis).await?;
LegacyProject::from_many(project, &**pool, &redis)
.await
.wrap_internal_err(
"executing `LegacyProject::from_many`",
)?;
Ok(HttpResponse::Ok().json(legacy_projects))
}
Err(response) => Ok(response),
@@ -284,14 +300,18 @@ pub async fn project_get(
.await
{
Ok(resp) => resp.0,
Err(ApiError::NotFound) => return Ok(HttpResponse::NotFound().body("")),
Err(ApiError::NotFound(_)) => {
return Ok(HttpResponse::NotFound().body(""));
}
Err(err) => return Err(err),
};
// Convert response to V2 format
let version_item = match project.versions.first() {
Some(vid) => {
version_item::DBVersion::get((*vid).into(), &**pool, &redis).await?
version_item::DBVersion::get((*vid).into(), &**pool, &redis)
.await
.wrap_internal_err("fetching version from database")?
}
None => None,
};
@@ -371,7 +391,8 @@ pub async fn dependency_list(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
match v2_reroute::extract_ok_json::<
crate::routes::v3::projects::DependencyInfo,
@@ -384,7 +405,10 @@ pub async fn dependency_list(
&**pool,
&redis,
)
.await?;
.await
.wrap_internal_err(
"converting dependency projects to legacy responses",
)?;
let converted_versions = dependency_info
.versions
.into_iter()
@@ -585,7 +609,9 @@ pub async fn project_edit(
if let Some(donation_urls) = v2_new_project.donation_urls {
// Fetch current donation links from project so we know what to delete
let fetched_example_project =
project_item::DBProject::get(&info.0, &**pool, &redis).await?;
project_item::DBProject::get(&info.0, &**pool, &redis)
.await
.wrap_api_err("fetching project from database")?;
let donation_links = fetched_example_project
.map(|x| {
x.urls
@@ -647,7 +673,8 @@ pub async fn project_edit(
search_state.clone(),
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// If client and server side were set, we will call
// the version setting route for each version to set the side types for each of them.
@@ -659,11 +686,13 @@ pub async fn project_edit(
&**pool,
&redis,
)
.await?;
.await
.wrap_api_err("fetching project from database")?;
let version_ids = project_item.map(|x| x.versions).unwrap_or_default();
let versions =
version_item::DBVersion::get_many(&version_ids, &**pool, &redis)
.await?;
.await
.wrap_internal_err("fetching versions from database")?;
for version in versions {
let version = Version::from(version);
let mut fields = version.fields;
@@ -690,7 +719,8 @@ pub async fn project_edit(
session_queue.clone(),
search_state.clone(),
)
.await?;
.await
.wrap_api_err("editing project")?;
}
}
Ok(response)
@@ -795,7 +825,9 @@ pub async fn projects_edit(
// If we are *setting* donation links, we will set every possible donation link to None, as
// setting will delete all of them then 're-add' the ones we want to keep
if let Some(donation_url) = bulk_edit_project.donation_urls {
let link_platforms = LinkPlatform::list(&**pool, &redis).await?;
let link_platforms = LinkPlatform::list(&**pool, &redis)
.await
.wrap_internal_err("fetching link platform from Redis")?;
for link in link_platforms {
if link.donation {
link_urls.insert(link.name, None);
+9 -4
View File
@@ -3,6 +3,7 @@ use crate::models::reports::Report;
use crate::models::v2::reports::LegacyReport;
use crate::queue::session::AuthQueue;
use crate::routes::{ApiError, v2_reroute, v3};
use crate::util::error::ApiContext as _;
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
use serde::Deserialize;
use validator::Validate;
@@ -44,7 +45,8 @@ pub async fn report_create(
let response =
v3::reports::report_create(req, pool, body, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Report>(response).await {
@@ -109,7 +111,8 @@ pub async fn reports(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Report>>(response).await {
@@ -164,7 +167,8 @@ pub async fn reports_get(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Report>>(response).await {
@@ -209,7 +213,8 @@ pub async fn report_get(
let response =
v3::reports::report_get(req, pool, redis, info, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Report>(response).await {
+3 -1
View File
@@ -3,6 +3,7 @@ use crate::routes::{
ApiError, v2_reroute,
v3::{self, statistics::V3Stats},
};
use crate::util::error::ApiContext as _;
use actix_web::{HttpResponse, get, web};
pub fn config(cfg: &mut actix_web::web::ServiceConfig) {
@@ -36,7 +37,8 @@ pub async fn get_stats(
) -> Result<HttpResponse, ApiError> {
let response = v3::statistics::get_stats(pool)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
match v2_reroute::extract_ok_json::<V3Stats>(response).await {
Ok(stats) => {
+14 -5
View File
@@ -1,3 +1,4 @@
use crate::util::error::ApiContext as _;
use std::collections::HashMap;
use super::ApiError;
@@ -54,7 +55,9 @@ pub async fn category_list(
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
let response = v3::tags::category_list(pool, redis).await?;
let response = v3::tags::category_list(pool, redis)
.await
.wrap_api_err("executing `tags::category_list`")?;
// Convert to V2 format
match v2_reroute::extract_ok_json::<Vec<v3::tags::CategoryData>>(response)
@@ -102,7 +105,9 @@ pub async fn loader_list(
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
let response = v3::tags::loader_list(pool, redis).await?;
let response = v3::tags::loader_list(pool, redis)
.await
.wrap_api_err("executing `tags::loader_list`")?;
// Convert to V2 format
match v2_reroute::extract_ok_json::<Vec<v3::tags::LoaderData>>(response)
@@ -198,7 +203,8 @@ pub async fn game_version_list(
}),
redis,
)
.await?;
.await
.wrap_api_err("fetching game versions")?;
// Convert to V2 format
Ok(
@@ -293,7 +299,8 @@ pub async fn license_text(
) -> Result<HttpResponse, ApiError> {
let license = v3::tags::license_text(params)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert to V2 format
Ok(
@@ -339,7 +346,9 @@ pub async fn donation_platform_list(
pool: web::Data<PgPool>,
redis: web::Data<RedisPool>,
) -> Result<HttpResponse, ApiError> {
let response = v3::tags::link_platform_list(pool, redis).await?;
let response = v3::tags::link_platform_list(pool, redis)
.await
.wrap_api_err("executing `tags::link_platform_list`")?;
// Convert to V2 format
Ok(
+10 -3
View File
@@ -6,6 +6,7 @@ use crate::models::teams::{
use crate::models::v2::teams::LegacyTeamMember;
use crate::queue::session::AuthQueue;
use crate::routes::{ApiError, v2_reroute, v3};
use crate::util::error::ApiContext as _;
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
use ariadne::ids::UserId;
use rust_decimal::Decimal;
@@ -63,7 +64,8 @@ pub async fn team_members_get_project(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<TeamMember>>(response).await {
Ok(members) => {
@@ -101,7 +103,8 @@ pub async fn team_members_get(
let response =
v3::teams::team_members_get(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<TeamMember>>(response).await {
Ok(members) => {
@@ -148,7 +151,11 @@ pub async fn teams_get(
.await
.or_else(v2_reroute::flatten_404_error);
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Vec<TeamMember>>>(response?).await {
match v2_reroute::extract_ok_json::<Vec<Vec<TeamMember>>>(
response.wrap_api_err("extracting v2 response body")?,
)
.await
{
Ok(members) => {
let members = members
.into_iter()
+3 -1
View File
@@ -5,6 +5,7 @@ use crate::models::threads::{MessageBody, Thread};
use crate::models::v2::threads::LegacyThread;
use crate::queue::session::AuthQueue;
use crate::routes::{ApiError, v2_reroute, v3};
use crate::util::error::ApiContext as _;
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
use serde::Deserialize;
use xredis::RedisPool;
@@ -88,7 +89,8 @@ pub async fn threads_get(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Thread>>(response).await {
+24 -8
View File
@@ -8,6 +8,8 @@ use crate::models::v2::projects::LegacyProject;
use crate::models::v2::user::LegacyUser;
use crate::queue::session::AuthQueue;
use crate::routes::{ApiError, v2_reroute, v3};
use crate::util::error::ApiContext as _;
use crate::util::error::Context as _;
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
use serde::{Deserialize, Serialize};
use validator::Validate;
@@ -53,7 +55,8 @@ pub async fn user_auth_get(
) -> Result<HttpResponse, ApiError> {
let response = v3::users::user_auth_get(req, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<User>(response).await {
@@ -96,7 +99,8 @@ pub async fn users_get(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<User>>(response).await {
@@ -136,7 +140,8 @@ pub async fn user_get(
) -> Result<HttpResponse, ApiError> {
let response = v3::users::user_get(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<User>(response).await {
@@ -181,13 +186,18 @@ pub async fn projects_list(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert to V2 projects
match v2_reroute::extract_ok_json::<Vec<Project>>(response).await {
Ok(project) => {
let legacy_projects =
LegacyProject::from_many(project, &**pool, &redis).await?;
LegacyProject::from_many(project, &**pool, &redis)
.await
.wrap_internal_err(
"executing `LegacyProject::from_many`",
)?;
Ok(HttpResponse::Ok().json(legacy_projects))
}
Err(response) => Ok(response),
@@ -448,13 +458,18 @@ pub async fn user_follows(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert to V2 projects
match v2_reroute::extract_ok_json::<Vec<Project>>(response).await {
Ok(project) => {
let legacy_projects =
LegacyProject::from_many(project, &**pool, &redis).await?;
LegacyProject::from_many(project, &**pool, &redis)
.await
.wrap_internal_err(
"executing `LegacyProject::from_many`",
)?;
Ok(HttpResponse::Ok().json(legacy_projects))
}
Err(response) => Ok(response),
@@ -494,7 +509,8 @@ pub async fn user_notifications(
let response =
v3::users::user_notifications(req, info, pool, redis, session_queue)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Notification>>(response).await {
Ok(notifications) => {
+20 -8
View File
@@ -7,6 +7,8 @@ use crate::queue::session::AuthQueue;
use crate::routes::HashAlgorithm;
use crate::routes::v3::version_file::{DownloadRedirect, HashQuery};
use crate::routes::{FileHash, v2_reroute, v3};
use crate::util::error::ApiContext as _;
use crate::util::error::Context as _;
use actix_web::{HttpRequest, HttpResponse, delete, get, post, web};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -69,7 +71,8 @@ pub async fn get_version_from_hash(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Version>(response).await {
@@ -233,7 +236,8 @@ pub async fn get_update_from_hash(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Version>(response).await {
@@ -289,7 +293,8 @@ pub async fn get_versions_from_hashes(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert to V2
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response)
@@ -342,7 +347,8 @@ pub async fn get_projects_from_hashes(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert to V2
match v2_reroute::extract_ok_json::<HashMap<String, Project>>(response)
@@ -361,7 +367,8 @@ pub async fn get_projects_from_hashes(
&**pool,
&redis,
)
.await?;
.await
.wrap_internal_err("converting projects to legacy responses")?;
let legacy_projects_hashes = hash_to_project_id
.into_iter()
.filter_map(|(hash, project_id)| {
@@ -426,7 +433,9 @@ pub async fn update_files(
.await
{
Ok(resp) => resp,
Err(ApiError::NotFound) => return Ok(HttpResponse::NotFound().body("")),
Err(ApiError::NotFound(_)) => {
return Ok(HttpResponse::NotFound().body(""));
}
Err(err) => return Err(err),
};
@@ -477,7 +486,9 @@ pub async fn update_files_many(
.await
{
Ok(resp) => resp,
Err(ApiError::NotFound) => return Ok(HttpResponse::NotFound().body("")),
Err(ApiError::NotFound(_)) => {
return Ok(HttpResponse::NotFound().body(""));
}
Err(err) => return Err(err),
};
@@ -565,7 +576,8 @@ pub async fn update_individual_files(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<HashMap<String, Version>>(response)
+19 -7
View File
@@ -1,3 +1,5 @@
use crate::util::error::ApiContext as _;
use crate::util::error::Context as _;
use std::collections::HashMap;
use super::ApiError;
@@ -138,7 +140,8 @@ pub async fn version_list(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Version>>(response).await {
@@ -191,7 +194,8 @@ pub async fn version_project_get(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Version>(response).await {
Ok(version) => {
@@ -242,7 +246,8 @@ pub async fn versions_get(
session_queue,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Vec<Version>>(response).await {
@@ -294,7 +299,8 @@ pub async fn version_get(
)
.await
.map(|b| HttpResponse::Ok().json(b))
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("flattening v2 not-found response")?;
// Convert response to V2 format
match v2_reroute::extract_ok_json::<Version>(response).await {
Ok(version) => {
@@ -396,7 +402,9 @@ pub async fn version_edit(
.await
{
Ok(resp) => resp,
Err(ApiError::NotFound) => return Ok(HttpResponse::NotFound().body("")),
Err(ApiError::NotFound(_)) => {
return Ok(HttpResponse::NotFound().body(""));
}
Err(err) => return Err(err),
};
let old_version = match v2_reroute::extract_ok_json::<Version>(
@@ -450,12 +458,16 @@ pub async fn version_edit(
info,
pool,
redis,
web::Json(serde_json::to_value(new_version)?),
web::Json(
serde_json::to_value(new_version)
.wrap_request_err("serializing version edit")?,
),
session_queue,
search_state,
)
.await
.or_else(v2_reroute::flatten_404_error)?;
.or_else(v2_reroute::flatten_404_error)
.wrap_api_err("editing version through v3 route")?;
Ok(response)
}