mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 17:28:24 -04:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b747c27b2 | ||
|
|
d5fa61ec3c | ||
|
|
c84dab45ca |
@@ -603,10 +603,8 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
AppStateSubscriptionProductKey,
|
||||
AppStateUserInfo,
|
||||
AppStateUserLocale,
|
||||
BatchGetInferenceJobStatusError,
|
||||
BatchGetInferenceJobStatusQueryParams,
|
||||
BatchGetInferenceJobStatusSuccessResponse,
|
||||
BatchGetMediaFilesError,
|
||||
BatchGetMediaFilesModelInfo,
|
||||
CreatePromptRequest,
|
||||
CreatePromptResponse,
|
||||
@@ -839,13 +837,11 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
GenerateVeo2Duration,
|
||||
GenerateVeo2ImageToVideoRequest,
|
||||
GenerateVeo2ImageToVideoResponse,
|
||||
GetInferenceJobStatusError,
|
||||
GetInferenceJobStatusPathInfo,
|
||||
GetInferenceJobStatusSuccessResponse,
|
||||
GetIsFeaturedItemError,
|
||||
GetIsFeaturedItemPathInfo,
|
||||
GetIsFeaturedItemSuccessResponse,
|
||||
GetMediaFileError,
|
||||
GetMediaFileModelInfo,
|
||||
GetMediaFileModeratorFields,
|
||||
GetMediaFilePathInfo,
|
||||
@@ -884,23 +880,18 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
ListDatasetsByUserError,
|
||||
ListDatasetsByUserPathInfo,
|
||||
ListDatasetsByUserSuccessResponse,
|
||||
ListFeaturedMediaFilesError,
|
||||
ListFeaturedMediaFilesQueryParams,
|
||||
ListFeaturedMediaFilesSuccessResponse,
|
||||
ListFeaturedWeightsError,
|
||||
ListFeaturedWeightsQueryParams,
|
||||
ListFeaturedWeightsSuccessResponse,
|
||||
ListMediaFilesByBatchError,
|
||||
ListMediaFilesByBatchPathInfo,
|
||||
ListMediaFilesByBatchSuccessResponse,
|
||||
ListMediaFilesError,
|
||||
ListMediaFilesForUserError,
|
||||
ListMediaFilesForUserPathInfo,
|
||||
ListMediaFilesForUserQueryParams,
|
||||
ListMediaFilesForUserSuccessResponse,
|
||||
ListMediaFilesQueryParams,
|
||||
ListMediaFilesSuccessResponse,
|
||||
ListPinnedMediaFilesError,
|
||||
ListPinnedMediaFilesSuccessResponse,
|
||||
ListPinnedWeightsError,
|
||||
ListPinnedWeightsSuccessResponse,
|
||||
|
||||
+16
-54
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::endpoints::inference_job::utils::estimates::estimate_job_progress::estimate_job_progress;
|
||||
@@ -10,12 +10,9 @@ use crate::http_server::endpoints::inference_job::utils::extractors::extract_liv
|
||||
use crate::http_server::endpoints::inference_job::utils::extractors::extract_polymorphic_inference_args::extract_polymorphic_inference_args;
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::filter_model_name::maybe_filter_model_name;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::state::server_state::ServerState;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use actix_web_lab::extract::Query;
|
||||
use artcraft_api_defs::common::responses::job_details::{JobDetailsLipsyncRequest, JobDetailsLivePortraitRequest};
|
||||
use artcraft_api_defs::common::responses::media_links::MediaLinks;
|
||||
@@ -26,7 +23,7 @@ use enums::by_table::generic_inference_jobs::frontend_failure_category::Frontend
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
|
||||
use log::error;
|
||||
use log::{error, warn};
|
||||
use mysql_queries::queries::generic_inference::web::batch_get_inference_job_status::batch_get_inference_job_status;
|
||||
use mysql_queries::queries::generic_inference::web::job_status::GenericInferenceJobStatus;
|
||||
use redis::Commands;
|
||||
@@ -143,37 +140,6 @@ pub struct BatchResultDetailsResponse {
|
||||
pub maybe_successfully_completed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum BatchGetInferenceJobStatusError {
|
||||
ServerError,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl ResponseError for BatchGetInferenceJobStatusError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
BatchGetInferenceJobStatusError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
BatchGetInferenceJobStatusError::NotFound => StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let error_reason = match self {
|
||||
Self::ServerError => "server error".to_string(),
|
||||
Self::NotFound => "not found".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using derive_more::Display since Clion doesn't understand it.
|
||||
impl fmt::Display for BatchGetInferenceJobStatusError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get job statuses for a batch of multiple job tokens.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -181,7 +147,7 @@ impl fmt::Display for BatchGetInferenceJobStatusError {
|
||||
path = "/v1/jobs/batch",
|
||||
responses(
|
||||
(status = 200, body = BatchGetInferenceJobStatusSuccessResponse),
|
||||
(status = 500, body = BatchGetInferenceJobStatusError),
|
||||
(status = 500),
|
||||
),
|
||||
params(
|
||||
BatchGetInferenceJobStatusQueryParams
|
||||
@@ -190,8 +156,8 @@ impl fmt::Display for BatchGetInferenceJobStatusError {
|
||||
pub async fn batch_get_inference_job_status_handler(
|
||||
http_request: HttpRequest,
|
||||
query: Query<BatchGetInferenceJobStatusQueryParams>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<BatchGetInferenceJobStatusSuccessResponse>, BatchGetInferenceJobStatusError> {
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<BatchGetInferenceJobStatusSuccessResponse>, AdvancedCommonWebError> {
|
||||
|
||||
let tokens = query.tokens.iter()
|
||||
.map(|token| token.trim())
|
||||
@@ -205,31 +171,27 @@ pub async fn batch_get_inference_job_status_handler(
|
||||
if tokens.is_empty() {
|
||||
// NB: MediaDomain doesn't matter since it's an empty list.
|
||||
return Ok(records_to_response(
|
||||
Vec::new(),
|
||||
server_state.server_environment,
|
||||
Vec::new(),
|
||||
server_state.server_environment,
|
||||
MediaDomain::Storyteller
|
||||
));
|
||||
}
|
||||
|
||||
// NB: Since this is publicly exposed, we don't query sensitive data.
|
||||
let maybe_records = batch_get_inference_job_status(
|
||||
let records = batch_get_inference_job_status(
|
||||
&tokens,
|
||||
&server_state.mysql_pool
|
||||
).await;
|
||||
|
||||
let records = match maybe_records {
|
||||
Ok(records) => records,
|
||||
Err(err) => {
|
||||
error!("tts job query error: {:?}", err);
|
||||
return Err(BatchGetInferenceJobStatusError::ServerError);
|
||||
}
|
||||
};
|
||||
).await
|
||||
.map_err(|err| {
|
||||
warn!("Batch job query error: {:?}", err);
|
||||
AdvancedCommonWebError::from_anyhow_error(err)
|
||||
})?;
|
||||
|
||||
let mut redis = server_state.redis_pool
|
||||
.get()
|
||||
.map_err(|e| {
|
||||
error!("redis error: {:?}", e);
|
||||
BatchGetInferenceJobStatusError::ServerError
|
||||
warn!("Redis pool error: {:?}", e);
|
||||
AdvancedCommonWebError::from_error(e)
|
||||
})?;
|
||||
|
||||
// TODO(bt,2024-04-22): Look up the extra redis statuses per item.
|
||||
|
||||
+14
-47
@@ -1,6 +1,6 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::endpoints::inference_job::utils::estimates::estimate_job_progress::estimate_job_progress;
|
||||
@@ -9,12 +9,9 @@ use crate::http_server::endpoints::inference_job::utils::extractors::extract_liv
|
||||
use crate::http_server::endpoints::inference_job::utils::extractors::extract_polymorphic_inference_args::extract_polymorphic_inference_args;
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::filter_model_name::maybe_filter_model_name;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::state::server_state::ServerState;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::{Json, Path};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::common::responses::job_details::{JobDetailsLipsyncRequest, JobDetailsLivePortraitRequest};
|
||||
use artcraft_api_defs::common::responses::media_links::MediaLinks;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
@@ -24,7 +21,7 @@ use enums::by_table::generic_inference_jobs::frontend_failure_category::Frontend
|
||||
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
|
||||
use enums::common::job_status_plus::JobStatusPlus;
|
||||
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
|
||||
use log::error;
|
||||
use log::{error, warn};
|
||||
use mysql_queries::queries::generic_inference::web::get_inference_job_status::get_inference_job_status;
|
||||
use mysql_queries::queries::generic_inference::web::job_status::GenericInferenceJobStatus;
|
||||
use redis::{Commands, RedisResult};
|
||||
@@ -134,37 +131,6 @@ pub struct ResultDetailsResponse {
|
||||
pub maybe_successfully_completed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum GetInferenceJobStatusError {
|
||||
ServerError,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl ResponseError for GetInferenceJobStatusError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
GetInferenceJobStatusError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
GetInferenceJobStatusError::NotFound => StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let error_reason = match self {
|
||||
Self::ServerError => "server error".to_string(),
|
||||
Self::NotFound => "not found".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using derive_more::Display since Clion doesn't understand it.
|
||||
impl fmt::Display for GetInferenceJobStatusError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the status for a single job.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -175,19 +141,20 @@ impl fmt::Display for GetInferenceJobStatusError {
|
||||
),
|
||||
responses(
|
||||
(status = 200, body = GetInferenceJobStatusSuccessResponse),
|
||||
(status = 500, body = GetInferenceJobStatusError),
|
||||
(status = 404),
|
||||
(status = 500),
|
||||
),
|
||||
)]
|
||||
pub async fn get_inference_job_status_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<GetInferenceJobStatusPathInfo>,
|
||||
server_state: web::Data<Arc<ServerState>>) -> Result<Json<GetInferenceJobStatusSuccessResponse>, GetInferenceJobStatusError>
|
||||
{
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<GetInferenceJobStatusSuccessResponse>, AdvancedCommonWebError> {
|
||||
if path.token.as_str().trim() == "None" {
|
||||
// NB: A bunch of Python clients use our API and can fail in this manner.
|
||||
// This was a large traffic driver during the 2023-03-08 outage.
|
||||
// Presumably it's this client: https://github.com/shards-7/fakeyou.py
|
||||
return Err(GetInferenceJobStatusError::NotFound);
|
||||
// Presumably, it's this client: https://github.com/shards-7/fakeyou.py
|
||||
return Err(AdvancedCommonWebError::NotFound);
|
||||
}
|
||||
|
||||
// NB: Since this is publicly exposed, we don't query sensitive data.
|
||||
@@ -198,18 +165,18 @@ pub async fn get_inference_job_status_handler(
|
||||
|
||||
let record = match maybe_status {
|
||||
Ok(Some(record)) => record,
|
||||
Ok(None) => return Err(GetInferenceJobStatusError::NotFound),
|
||||
Ok(None) => return Err(AdvancedCommonWebError::NotFound),
|
||||
Err(err) => {
|
||||
error!("tts job query error: {:?}", err);
|
||||
return Err(GetInferenceJobStatusError::ServerError);
|
||||
warn!("Job query error for token {}: {:?}", path.token.as_str(), err);
|
||||
return Err(AdvancedCommonWebError::from_anyhow_error(err));
|
||||
}
|
||||
};
|
||||
|
||||
let mut redis = server_state.redis_pool
|
||||
.get()
|
||||
.map_err(|e| {
|
||||
error!("redis error: {:?}", e);
|
||||
GetInferenceJobStatusError::ServerError
|
||||
warn!("Redis pool error: {:?}", e);
|
||||
AdvancedCommonWebError::from_error(e)
|
||||
})?;
|
||||
|
||||
// TODO(bt,2023-05-21): Make async.
|
||||
|
||||
+7
-47
@@ -1,11 +1,8 @@
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use actix_web_lab::extract::Query;
|
||||
use artcraft_api_defs::common::responses::media_links::MediaLinks;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
@@ -27,6 +24,7 @@ use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
|
||||
@@ -34,7 +32,6 @@ use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
|
||||
use crate::http_server::endpoints::media_files::common_responses::live_portrait::MediaFileLivePortraitDetails;
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::bucket_urls::bucket_url_string_from_media_path::bucket_url_string_from_media_path;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
// =============== Request ===============
|
||||
@@ -197,39 +194,6 @@ pub struct BatchGetMediaFilesModelInfo {
|
||||
pub maybe_weight_creator: Option<UserDetailsLight>,
|
||||
}
|
||||
|
||||
// =============== Error Response ===============
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum BatchGetMediaFilesError {
|
||||
ServerError,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl ResponseError for BatchGetMediaFilesError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
BatchGetMediaFilesError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
BatchGetMediaFilesError::NotFound => StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let error_reason = match self {
|
||||
BatchGetMediaFilesError::ServerError => "server error".to_string(),
|
||||
BatchGetMediaFilesError::NotFound => "not found".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using derive_more::Display since Clion doesn't understand it.
|
||||
impl fmt::Display for BatchGetMediaFilesError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
// =============== Handler ===============
|
||||
|
||||
/// List many media files at the same time by supplying their tokens. You'll have to provide the tokens.
|
||||
@@ -239,8 +203,8 @@ impl fmt::Display for BatchGetMediaFilesError {
|
||||
path = "/v1/media_files/batch",
|
||||
responses(
|
||||
(status = 200, description = "Found", body = BatchGetMediaFilesSuccessResponse),
|
||||
(status = 404, description = "Not found", body = BatchGetMediaFilesError),
|
||||
(status = 500, description = "Server error", body = BatchGetMediaFilesError),
|
||||
(status = 404, description = "Not found"),
|
||||
(status = 500, description = "Server error"),
|
||||
),
|
||||
params(
|
||||
BatchGetMediaFilesQueryParams,
|
||||
@@ -250,15 +214,11 @@ pub async fn batch_get_media_files_handler(
|
||||
http_request: HttpRequest,
|
||||
query: Query<BatchGetMediaFilesQueryParams>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<BatchGetMediaFilesSuccessResponse>, BatchGetMediaFilesError> {
|
||||
) -> Result<Json<BatchGetMediaFilesSuccessResponse>, AdvancedCommonWebError> {
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session(&http_request, &server_state.mysql_pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
BatchGetMediaFilesError::ServerError
|
||||
})?;
|
||||
.await?;
|
||||
|
||||
let mut show_deleted_results = false;
|
||||
let mut is_moderator = false;
|
||||
@@ -288,7 +248,7 @@ pub async fn batch_get_media_files_handler(
|
||||
Ok(media_files) => media_files,
|
||||
Err(e) => {
|
||||
warn!("query error: {:?}", e);
|
||||
return Err(BatchGetMediaFilesError::ServerError);
|
||||
return Err(AdvancedCommonWebError::from_anyhow_error(e));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+14
-52
@@ -1,6 +1,6 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
@@ -10,12 +10,9 @@ use crate::http_server::endpoints::media_files::common_responses::live_portrait:
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::bucket_urls::bucket_url_from_media_path::bucket_url_from_media_path;
|
||||
use crate::http_server::web_utils::bucket_urls::bucket_url_from_str_path::bucket_url_from_str_path;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::state::server_state::ServerState;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::{Json, Path};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::common::responses::media_links::MediaLinks;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -202,37 +199,6 @@ pub struct GetMediaFileModelInfo {
|
||||
pub maybe_weight_creator: Option<UserDetailsLight>,
|
||||
}
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum GetMediaFileError {
|
||||
ServerError,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl ResponseError for GetMediaFileError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
GetMediaFileError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
GetMediaFileError::NotFound => StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let error_reason = match self {
|
||||
GetMediaFileError::ServerError => "server error".to_string(),
|
||||
GetMediaFileError::NotFound => "not found".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using derive_more::Display since Clion doesn't understand it.
|
||||
impl fmt::Display for GetMediaFileError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a single media file by token.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -240,8 +206,8 @@ impl fmt::Display for GetMediaFileError {
|
||||
path = "/v1/media_files/file/{token}",
|
||||
responses(
|
||||
(status = 200, description = "Found", body = GetMediaFileSuccessResponse),
|
||||
(status = 404, description = "Not found", body = GetMediaFileError),
|
||||
(status = 500, description = "Server error", body = GetMediaFileError),
|
||||
(status = 404, description = "Not found"),
|
||||
(status = 500, description = "Server error"),
|
||||
),
|
||||
params(
|
||||
("path" = GetMediaFilePathInfo, description = "Path for Request")
|
||||
@@ -251,17 +217,13 @@ pub async fn get_media_file_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<GetMediaFilePathInfo>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<GetMediaFileSuccessResponse>, GetMediaFileError> {
|
||||
) -> Result<Json<GetMediaFileSuccessResponse>, AdvancedCommonWebError> {
|
||||
let media_file_token = path.into_inner().token;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session(&http_request, &server_state.mysql_pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
GetMediaFileError::ServerError
|
||||
})?;
|
||||
.await?;
|
||||
|
||||
let mut show_deleted_results = false;
|
||||
let mut is_moderator = false;
|
||||
@@ -305,7 +267,7 @@ async fn modern_media_file_lookup(
|
||||
show_deleted_results: bool,
|
||||
server_state: &ServerState,
|
||||
media_domain: MediaDomain,
|
||||
) -> Result<GetMediaFileSuccessResponse, GetMediaFileError> {
|
||||
) -> Result<GetMediaFileSuccessResponse, AdvancedCommonWebError> {
|
||||
|
||||
let is_mod = show_deleted_results;
|
||||
|
||||
@@ -318,9 +280,9 @@ async fn modern_media_file_lookup(
|
||||
let result = match result {
|
||||
Err(e) => {
|
||||
warn!("query error: {:?}", e);
|
||||
return Err(GetMediaFileError::ServerError);
|
||||
return Err(AdvancedCommonWebError::from_anyhow_error(e));
|
||||
}
|
||||
Ok(None) => return Err(GetMediaFileError::NotFound),
|
||||
Ok(None) => return Err(AdvancedCommonWebError::NotFound),
|
||||
Ok(Some(result)) => result,
|
||||
};
|
||||
|
||||
@@ -368,7 +330,7 @@ async fn modern_media_file_lookup(
|
||||
public_bucket_url: bucket_url_from_media_path(&public_bucket_path, media_domain, server_state.server_environment)
|
||||
.map_err(|err| {
|
||||
warn!("error creating URL: {:?}", err);
|
||||
GetMediaFileError::ServerError
|
||||
AdvancedCommonWebError::from_anyhow_error(err)
|
||||
})?,
|
||||
public_bucket_path: public_bucket_path
|
||||
.get_full_object_path_str()
|
||||
@@ -453,7 +415,7 @@ async fn emulate_media_file_with_legacy_tts_result_lookup(
|
||||
show_deleted_results: bool,
|
||||
server_state: &ServerState,
|
||||
media_domain: MediaDomain,
|
||||
) -> Result<GetMediaFileSuccessResponse, GetMediaFileError> {
|
||||
) -> Result<GetMediaFileSuccessResponse, AdvancedCommonWebError> {
|
||||
|
||||
let result = select_tts_result_by_token(
|
||||
&media_file_token.as_str(),
|
||||
@@ -464,9 +426,9 @@ async fn emulate_media_file_with_legacy_tts_result_lookup(
|
||||
let result = match result {
|
||||
Err(e) => {
|
||||
warn!("query error (legacy TTS): {:?}", e);
|
||||
return Err(GetMediaFileError::ServerError);
|
||||
return Err(AdvancedCommonWebError::from_anyhow_error(e));
|
||||
}
|
||||
Ok(None) => return Err(GetMediaFileError::NotFound),
|
||||
Ok(None) => return Err(AdvancedCommonWebError::NotFound),
|
||||
Ok(Some(result)) => result,
|
||||
};
|
||||
|
||||
@@ -506,7 +468,7 @@ async fn emulate_media_file_with_legacy_tts_result_lookup(
|
||||
public_bucket_url: bucket_url_from_str_path(&public_bucket_path, media_domain, server_state.server_environment)
|
||||
.map_err(|err| {
|
||||
warn!("error creating URL: {:?}", err);
|
||||
GetMediaFileError::ServerError
|
||||
AdvancedCommonWebError::from_anyhow_error(err)
|
||||
})?,
|
||||
public_bucket_path,
|
||||
cover_image: MediaFileCoverImageDetails::from_legacy_token_str(&result.tts_result_token),
|
||||
|
||||
+5
-14
@@ -1,19 +1,14 @@
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::media::media_file_cover_image_details_builder::MediaFileCoverImageDetailsBuilder;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::common_responses::user_details_lite_builder::UserDetailsLightBuilder;
|
||||
use crate::http_server::endpoints::media_files::get::get_media_file_handler::GetMediaFilePathInfo;
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::state::server_state::ServerState;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::{Json, Path};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use actix_web_lab::extract::Query;
|
||||
use artcraft_api_defs::common::responses::media_links::MediaLinks;
|
||||
use artcraft_api_defs::common::responses::simple_entity_stats::SimpleEntityStats;
|
||||
@@ -45,7 +40,7 @@ pub async fn list_batch_generated_redux_media_files_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<ListBatchGeneratedReduxMediaFilesPathInfo>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<ListBatchGeneratedReduxMediaFilesSuccessResponse>, CommonWebError> {
|
||||
) -> Result<Json<ListBatchGeneratedReduxMediaFilesSuccessResponse>, AdvancedCommonWebError> {
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
.acquire()
|
||||
.await?;
|
||||
@@ -53,11 +48,7 @@ pub async fn list_batch_generated_redux_media_files_handler(
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
.await?;
|
||||
|
||||
let mut show_deleted_results = false;
|
||||
let mut is_moderator = false;
|
||||
@@ -83,7 +74,7 @@ pub async fn list_batch_generated_redux_media_files_handler(
|
||||
Ok(batch) => batch.media_files,
|
||||
Err(e) => {
|
||||
warn!("query error: {:?}", e);
|
||||
return Err(CommonWebError::ServerError);
|
||||
return Err(AdvancedCommonWebError::from_anyhow_error(e));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+10
-45
@@ -1,5 +1,3 @@
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Query;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use actix_web_lab::__reexports::tracing::info;
|
||||
@@ -23,6 +21,7 @@ use time::ext::InstantExt;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails;
|
||||
@@ -182,27 +181,6 @@ pub struct FeaturedMediaFile {
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum ListFeaturedMediaFilesError {
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ListFeaturedMediaFilesError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for ListFeaturedMediaFilesError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
ListFeaturedMediaFilesError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
ListFeaturedMediaFilesError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// List media files that were featured or promoted by the staff (paginated). This will power our global asset drawers and highlight reels.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -211,15 +189,15 @@ impl ResponseError for ListFeaturedMediaFilesError {
|
||||
params(ListFeaturedMediaFilesQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "List Featured Media Files", body = ListFeaturedMediaFilesSuccessResponse),
|
||||
(status = 401, description = "Not authorized", body = ListFeaturedMediaFilesError),
|
||||
(status = 500, description = "Server error", body = ListFeaturedMediaFilesError),
|
||||
(status = 401, description = "Not authorized"),
|
||||
(status = 500, description = "Server error"),
|
||||
),
|
||||
)]
|
||||
pub async fn list_featured_media_files_handler(
|
||||
http_request: HttpRequest,
|
||||
query: Query<ListFeaturedMediaFilesQueryParams>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, ListFeaturedMediaFilesError> {
|
||||
) -> Result<HttpResponse, AdvancedCommonWebError> {
|
||||
|
||||
let cache_start = Instant::now();
|
||||
|
||||
@@ -246,22 +224,14 @@ pub async fn list_featured_media_files_handler(
|
||||
};
|
||||
|
||||
let cursor_next = if let Some(id) = results_page.last_id {
|
||||
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)
|
||||
.map_err(|e| {
|
||||
warn!("crypto error: {:?}", e);
|
||||
ListFeaturedMediaFilesError::ServerError
|
||||
})?;
|
||||
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)?;
|
||||
Some(cursor)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cursor_previous = if let Some(id) = results_page.first_id {
|
||||
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)
|
||||
.map_err(|e| {
|
||||
warn!("crypto error: {:?}", e);
|
||||
ListFeaturedMediaFilesError::ServerError
|
||||
})?;
|
||||
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)?;
|
||||
Some(cursor)
|
||||
} else {
|
||||
None
|
||||
@@ -349,8 +319,7 @@ pub async fn list_featured_media_files_handler(
|
||||
}
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|e| ListFeaturedMediaFilesError::ServerError)?;
|
||||
let body = serde_json::to_string(&response)?;
|
||||
|
||||
let mut response_builder = HttpResponse::Ok();
|
||||
|
||||
@@ -368,7 +337,7 @@ async fn database_lookup(
|
||||
query: &ListFeaturedMediaFilesQueryParams,
|
||||
server_state: &ServerState,
|
||||
maybe_cloudflare_header: Option<&str>,
|
||||
) -> Result<FeaturedMediaFileListPage, ListFeaturedMediaFilesError> {
|
||||
) -> Result<FeaturedMediaFileListPage, AdvancedCommonWebError> {
|
||||
|
||||
// TODO(bt,2023-12-04): Enforce real maximums and defaults
|
||||
let limit = query.page_size.unwrap_or(25);
|
||||
@@ -377,11 +346,7 @@ async fn database_lookup(
|
||||
let cursor_is_reversed = query.cursor_is_reversed.unwrap_or(false);
|
||||
|
||||
let cursor = if let Some(cursor) = query.cursor.as_deref() {
|
||||
let cursor = server_state.sort_key_crypto.decrypt_id(cursor)
|
||||
.map_err(|e| {
|
||||
warn!("crypto error: {:?}", e);
|
||||
ListFeaturedMediaFilesError::ServerError
|
||||
})?;
|
||||
let cursor = server_state.sort_key_crypto.decrypt_id(cursor)?;
|
||||
Some(cursor as usize)
|
||||
} else {
|
||||
None
|
||||
@@ -411,7 +376,7 @@ async fn database_lookup(
|
||||
mysql_pool: &server_state.mysql_pool,
|
||||
}).await.map_err(|err| {
|
||||
error!("DB error: {:?}", err);
|
||||
ListFeaturedMediaFilesError::ServerError
|
||||
AdvancedCommonWebError::from_anyhow_error(err)
|
||||
})?;
|
||||
|
||||
let query_duration = Instant::now().signed_duration_since(query_start);
|
||||
|
||||
+6
-39
@@ -1,9 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::{Json, Path, Query};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use artcraft_api_defs::common::responses::media_links::MediaLinks;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -23,6 +21,7 @@ use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails;
|
||||
@@ -30,7 +29,6 @@ use crate::http_server::common_responses::pagination_page::PaginationPage;
|
||||
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::bucket_urls::bucket_url_string_from_media_path::bucket_url_string_from_media_path;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::allowed_studio_access::allowed_studio_access;
|
||||
|
||||
@@ -140,33 +138,6 @@ pub struct MediaFilesByBatchListItem {
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum ListMediaFilesByBatchError {
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl ResponseError for ListMediaFilesByBatchError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
ListMediaFilesByBatchError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let error_reason = match self {
|
||||
ListMediaFilesByBatchError::ServerError => "server error".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ListMediaFilesByBatchError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// List media files that were generated together in a "batch", e.g. stable diffusion outputs.
|
||||
///
|
||||
/// This uses a special "batch token" that these jobs return. Media files created together will also
|
||||
@@ -180,7 +151,7 @@ impl std::fmt::Display for ListMediaFilesByBatchError {
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List Media Files by Batch", body = ListMediaFilesByBatchSuccessResponse),
|
||||
(status = 500, description = "Server error", body = ListMediaFilesByBatchError),
|
||||
(status = 500, description = "Server error"),
|
||||
),
|
||||
)]
|
||||
pub async fn list_media_files_by_batch_token_handler(
|
||||
@@ -188,16 +159,12 @@ pub async fn list_media_files_by_batch_token_handler(
|
||||
path: Path<ListMediaFilesByBatchPathInfo>,
|
||||
query: Query<ListMediaFilesByBatchQueryParams>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<ListMediaFilesByBatchSuccessResponse>, ListMediaFilesByBatchError>
|
||||
) -> Result<Json<ListMediaFilesByBatchSuccessResponse>, AdvancedCommonWebError>
|
||||
{
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session(&http_request, &server_state.mysql_pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
ListMediaFilesByBatchError::ServerError
|
||||
})?;
|
||||
.await?;
|
||||
|
||||
// NB: Temporary rollout flag for certain file types (BVH, etc).
|
||||
let mut is_allowed_studio_access = allowed_studio_access(
|
||||
@@ -226,7 +193,7 @@ pub async fn list_media_files_by_batch_token_handler(
|
||||
Ok(results) => results,
|
||||
Err(e) => {
|
||||
warn!("Query error: {:?}", e);
|
||||
return Err(ListMediaFilesByBatchError::ServerError);
|
||||
return Err(AdvancedCommonWebError::from_anyhow_error(e));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+9
-49
@@ -1,8 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::{Path, Query};
|
||||
use actix_web::web::{Json, Path, Query};
|
||||
use actix_web::{web, HttpMessage, HttpRequest, HttpResponse};
|
||||
use artcraft_api_defs::common::responses::media_links::MediaLinks;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
@@ -22,6 +20,7 @@ use mysql_queries::queries::media_files::list::list_media_files_for_user::{list_
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails;
|
||||
@@ -32,7 +31,6 @@ use crate::http_server::endpoints::media_files::helpers::get_scoped_engine_categ
|
||||
use crate::http_server::endpoints::media_files::helpers::get_scoped_media_classes::get_scoped_media_classes;
|
||||
use crate::http_server::endpoints::media_files::helpers::get_scoped_media_types::get_scoped_media_types;
|
||||
use crate::http_server::web_utils::bucket_urls::bucket_url_string_from_media_path::bucket_url_string_from_media_path;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::allowed_studio_access::allowed_studio_access;
|
||||
|
||||
@@ -175,33 +173,6 @@ pub struct MediaFileForUserListItem {
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum ListMediaFilesForUserError {
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl ResponseError for ListMediaFilesForUserError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
ListMediaFilesForUserError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let error_reason = match self {
|
||||
ListMediaFilesForUserError::ServerError => "server error".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ListMediaFilesForUserError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// List all of a user's media files (paginated).
|
||||
///
|
||||
/// This endpoint uses the session to automatically show all files for the given user, but only
|
||||
@@ -215,7 +186,7 @@ impl std::fmt::Display for ListMediaFilesForUserError {
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List Featured Media Files", body = ListMediaFilesForUserSuccessResponse),
|
||||
(status = 500, description = "Server error", body = ListMediaFilesForUserError),
|
||||
(status = 500, description = "Server error"),
|
||||
),
|
||||
)]
|
||||
pub async fn list_media_files_for_user_handler(
|
||||
@@ -223,16 +194,12 @@ pub async fn list_media_files_for_user_handler(
|
||||
path: Path<ListMediaFilesForUserPathInfo>,
|
||||
query: Query<ListMediaFilesForUserQueryParams>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, ListMediaFilesForUserError>
|
||||
) -> Result<Json<ListMediaFilesForUserSuccessResponse>, AdvancedCommonWebError>
|
||||
{
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session(&http_request, &server_state.mysql_pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
ListMediaFilesForUserError::ServerError
|
||||
})?;
|
||||
.await?;
|
||||
|
||||
let mut is_author = false;
|
||||
let mut is_mod = false;
|
||||
@@ -287,7 +254,7 @@ pub async fn list_media_files_for_user_handler(
|
||||
Ok(results) => results,
|
||||
Err(e) => {
|
||||
warn!("Query error: {:?}", e);
|
||||
return Err(ListMediaFilesForUserError::ServerError);
|
||||
return Err(AdvancedCommonWebError::from_anyhow_error(e));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -372,19 +339,12 @@ pub async fn list_media_files_for_user_handler(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let response = ListMediaFilesForUserSuccessResponse {
|
||||
Ok(Json(ListMediaFilesForUserSuccessResponse {
|
||||
success: true,
|
||||
results,
|
||||
pagination: PaginationPage{
|
||||
pagination: PaginationPage {
|
||||
current: results_page.current_page,
|
||||
total_page_count: results_page.total_page_count,
|
||||
}
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|e| ListMediaFilesForUserError::ServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}))
|
||||
}
|
||||
|
||||
+14
-68
@@ -1,8 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::Query;
|
||||
use actix_web::web::{Json, Query};
|
||||
use actix_web::{web, HttpMessage, HttpRequest, HttpResponse};
|
||||
use artcraft_api_defs::common::responses::media_links::MediaLinks;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
@@ -11,17 +9,18 @@ use enums::by_table::media_files::media_file_animation_type::MediaFileAnimationT
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
|
||||
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
|
||||
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
|
||||
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::view_as::ViewAs;
|
||||
use enums::common::visibility::Visibility;
|
||||
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
|
||||
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
|
||||
use log::warn;
|
||||
use mysql_queries::queries::media_files::list::list_media_files::{list_media_files, ListMediaFilesArgs};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails;
|
||||
@@ -33,7 +32,6 @@ use crate::http_server::endpoints::media_files::helpers::get_scoped_engine_categ
|
||||
use crate::http_server::endpoints::media_files::helpers::get_scoped_media_classes::get_scoped_media_classes;
|
||||
use crate::http_server::endpoints::media_files::helpers::get_scoped_media_types::get_scoped_media_types;
|
||||
use crate::http_server::web_utils::bucket_urls::bucket_url_string_from_media_path::bucket_url_string_from_media_path;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::allowed_explore_media_access::allowed_explore_media_access;
|
||||
|
||||
@@ -174,36 +172,6 @@ pub struct MediaFileListItem {
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum ListMediaFilesError {
|
||||
ServerError,
|
||||
NotAuthorized,
|
||||
}
|
||||
|
||||
impl ResponseError for ListMediaFilesError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
ListMediaFilesError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ListMediaFilesError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let error_reason = match self {
|
||||
ListMediaFilesError::ServerError => "server error".to_string(),
|
||||
ListMediaFilesError::NotAuthorized => "not authorized".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ListMediaFilesError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// List all media files in the system globally, across all users (paginated).
|
||||
///
|
||||
/// This powers "explore files" page.
|
||||
@@ -214,29 +182,26 @@ impl std::fmt::Display for ListMediaFilesError {
|
||||
params(ListMediaFilesQueryParams),
|
||||
responses(
|
||||
(status = 200, description = "List Featured Media Files", body = ListMediaFilesSuccessResponse),
|
||||
(status = 500, description = "Server error", body = ListMediaFilesError),
|
||||
(status = 401, description = "Not authorized"),
|
||||
(status = 500, description = "Server error"),
|
||||
),
|
||||
)]
|
||||
pub async fn list_media_files_handler(
|
||||
http_request: HttpRequest,
|
||||
query: Query<ListMediaFilesQueryParams>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, ListMediaFilesError>
|
||||
) -> Result<Json<ListMediaFilesSuccessResponse>, AdvancedCommonWebError>
|
||||
{
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session(&http_request, &server_state.mysql_pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
ListMediaFilesError::ServerError
|
||||
})?;
|
||||
.await?;
|
||||
|
||||
// ==================== FEATURE FLAG CHECK ==================== //
|
||||
|
||||
if !allowed_explore_media_access(maybe_user_session.as_ref()) {
|
||||
warn!("Explore media access is not permitted for user");
|
||||
return Err(ListMediaFilesError::NotAuthorized);
|
||||
return Err(AdvancedCommonWebError::NotAuthorized);
|
||||
}
|
||||
|
||||
let mut is_mod = false;
|
||||
@@ -255,11 +220,7 @@ pub async fn list_media_files_handler(
|
||||
let cursor_is_reversed = query.cursor_is_reversed.unwrap_or(false);
|
||||
|
||||
let cursor = if let Some(cursor) = query.cursor.as_deref() {
|
||||
let cursor = server_state.sort_key_crypto.decrypt_id(cursor)
|
||||
.map_err(|e| {
|
||||
warn!("crypto error: {:?}", e);
|
||||
ListMediaFilesError::ServerError
|
||||
})?;
|
||||
let cursor = server_state.sort_key_crypto.decrypt_id(cursor)?;
|
||||
Some(cursor as usize)
|
||||
} else {
|
||||
None
|
||||
@@ -293,27 +254,19 @@ pub async fn list_media_files_handler(
|
||||
Ok(results) => results,
|
||||
Err(e) => {
|
||||
warn!("Query error: {:?}", e);
|
||||
return Err(ListMediaFilesError::ServerError);
|
||||
return Err(AdvancedCommonWebError::from_anyhow_error(e));
|
||||
}
|
||||
};
|
||||
|
||||
let cursor_next = if let Some(id) = results_page.last_id {
|
||||
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)
|
||||
.map_err(|e| {
|
||||
warn!("crypto error: {:?}", e);
|
||||
ListMediaFilesError::ServerError
|
||||
})?;
|
||||
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)?;
|
||||
Some(cursor)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cursor_previous = if let Some(id) = results_page.first_id {
|
||||
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)
|
||||
.map_err(|e| {
|
||||
warn!("crypto error: {:?}", e);
|
||||
ListMediaFilesError::ServerError
|
||||
})?;
|
||||
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)?;
|
||||
Some(cursor)
|
||||
} else {
|
||||
None
|
||||
@@ -387,7 +340,7 @@ pub async fn list_media_files_handler(
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let response = ListMediaFilesSuccessResponse {
|
||||
Ok(Json(ListMediaFilesSuccessResponse {
|
||||
success: true,
|
||||
results,
|
||||
pagination: PaginationCursors {
|
||||
@@ -395,12 +348,5 @@ pub async fn list_media_files_handler(
|
||||
maybe_previous: cursor_previous,
|
||||
cursor_is_reversed,
|
||||
}
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|e| ListMediaFilesError::ServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}))
|
||||
}
|
||||
|
||||
+10
-38
@@ -1,8 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use actix_web::web::Json;
|
||||
use artcraft_api_defs::common::responses::media_links::MediaLinks;
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -20,6 +19,7 @@ use redis::Commands;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError;
|
||||
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails;
|
||||
@@ -122,27 +122,6 @@ pub struct PinnedMediaFile {
|
||||
/// The key we store pinned media file tokens under
|
||||
const REDIS_KEY : &str = "pinned_media_files_list";
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum ListPinnedMediaFilesError {
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ListPinnedMediaFilesError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for ListPinnedMediaFilesError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
ListPinnedMediaFilesError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
ListPinnedMediaFilesError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// List media files that were pinned by the staff (paginated).
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -150,25 +129,25 @@ impl ResponseError for ListPinnedMediaFilesError {
|
||||
path = "/v1/media_files/list_pinned",
|
||||
responses(
|
||||
(status = 200, description = "List Featured Media Files", body = ListPinnedMediaFilesSuccessResponse),
|
||||
(status = 401, description = "Not authorized", body = ListPinnedMediaFilesError),
|
||||
(status = 500, description = "Server error", body = ListPinnedMediaFilesError),
|
||||
(status = 401, description = "Not authorized"),
|
||||
(status = 500, description = "Server error"),
|
||||
),
|
||||
)]
|
||||
pub async fn list_pinned_media_files_handler(
|
||||
http_request: HttpRequest,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, impl ResponseError> {
|
||||
) -> Result<Json<ListPinnedMediaFilesSuccessResponse>, AdvancedCommonWebError> {
|
||||
|
||||
let mut redis = server_state.redis_pool.get()
|
||||
.map_err(|err| {
|
||||
error!("Could not obtain redis: {err}");
|
||||
ListPinnedMediaFilesError::ServerError
|
||||
AdvancedCommonWebError::server_error_with_message("pinned media files error")
|
||||
})?;
|
||||
|
||||
let token_list : Option<String> = redis.get(REDIS_KEY)
|
||||
.map_err(|err| {
|
||||
error!("Could not get redis result: {err}");
|
||||
ListPinnedMediaFilesError::ServerError
|
||||
AdvancedCommonWebError::server_error_with_message("pinned media files error")
|
||||
})?;
|
||||
|
||||
let media_file_tokens = token_list
|
||||
@@ -192,14 +171,14 @@ pub async fn list_pinned_media_files_handler(
|
||||
Ok(media_files) => media_files,
|
||||
Err(e) => {
|
||||
warn!("Query error: {:?}", e);
|
||||
return Err(ListPinnedMediaFilesError::ServerError);
|
||||
return Err(AdvancedCommonWebError::from_anyhow_error(e));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let media_domain = get_media_domain(&http_request);
|
||||
|
||||
let response = ListPinnedMediaFilesSuccessResponse {
|
||||
Ok(Json(ListPinnedMediaFilesSuccessResponse {
|
||||
success: true,
|
||||
results: media_files.into_iter()
|
||||
.map(|m| {
|
||||
@@ -265,12 +244,5 @@ pub async fn list_pinned_media_files_handler(
|
||||
updated_at: m.updated_at,
|
||||
}
|
||||
}).collect::<Vec<_>>(),
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|e| ListPinnedMediaFilesError::ServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}))
|
||||
}
|
||||
|
||||
+180
-1
@@ -1,4 +1,6 @@
|
||||
use log::{debug, warn};
|
||||
use regex::Regex;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use pager::client::pager::Pager;
|
||||
use pager::notification::notification_details_builder::NotificationDetailsBuilder;
|
||||
@@ -21,7 +23,13 @@ pub(crate) fn check_advanced_common_web_error(
|
||||
return true;
|
||||
}
|
||||
|
||||
let title = format!("UncaughtServerError on {} {}", method, path);
|
||||
let maybe_error_name = error.cause()
|
||||
.and_then(|cause| try_error_title(cause));
|
||||
|
||||
let title = match maybe_error_name {
|
||||
Some(name) => format!("{} on {} {}", name, method, path),
|
||||
None => format!("UncaughtServerError on {} {}", method, path),
|
||||
};
|
||||
|
||||
// Clone the Arc'd causal error so the notification owns a reference to it.
|
||||
let cause_arc = error.clone_cause_arc();
|
||||
@@ -53,3 +61,174 @@ pub(crate) fn check_advanced_common_web_error(
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Try to extract a human-readable error title from the causal error's Debug
|
||||
/// representation. Looks for CamelCase type names like `EnvError::NotUnicode`.
|
||||
fn try_error_title(error: &dyn std::error::Error) -> Option<String> {
|
||||
let debug_str = format!("{:?}", error);
|
||||
try_extract_error_name(&debug_str)
|
||||
}
|
||||
|
||||
/// Regex that matches CamelCase type names with an optional `::Variant` suffix.
|
||||
/// E.g. `CommonWebError::InvalidRequest`, `EnvError`, `KinoviError::RequestError`.
|
||||
static ERROR_NAME_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"[A-Z][0-9a-z]+(?:[A-Z][0-9a-z]+)*(?:::[A-Z][0-9a-z]+(?:[A-Z][0-9a-z]+)*)?")
|
||||
.expect("error name regex should compile")
|
||||
});
|
||||
|
||||
/// Extract the leading CamelCase error name (with optional `::Variant`) from
|
||||
/// a Debug-formatted error string. Returns `None` if no match is found.
|
||||
///
|
||||
/// ```text
|
||||
/// "CommonWebError::InvalidRequest(\"bad\")" → Some("CommonWebError::InvalidRequest")
|
||||
/// "EnvError::ParseError { reason: \"foo\" }" → Some("EnvError::ParseError")
|
||||
/// "just a plain string" → None
|
||||
/// ```
|
||||
fn try_extract_error_name(debug_str: &str) -> Option<String> {
|
||||
ERROR_NAME_RE.find(debug_str)
|
||||
.map(|m| m.as_str().to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// --- try_extract_error_name tests ---
|
||||
|
||||
#[test]
|
||||
fn extract_just_error_name() {
|
||||
assert_eq!(
|
||||
try_extract_error_name("JustErrorName"),
|
||||
Some("JustErrorName".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_error_with_variant_and_string_payload() {
|
||||
assert_eq!(
|
||||
try_extract_error_name(r#"CommonWebError::InvalidRequest("Invalid request")"#),
|
||||
Some("CommonWebError::InvalidRequest".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_error_with_unit_variant() {
|
||||
assert_eq!(
|
||||
try_extract_error_name("EnvError::NotUnicode"),
|
||||
Some("EnvError::NotUnicode".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_error_with_struct_payload() {
|
||||
assert_eq!(
|
||||
try_extract_error_name(r#"EnvError::ParseError { reason: "foo" }"#),
|
||||
Some("EnvError::ParseError".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_error_with_tuple_payload() {
|
||||
assert_eq!(
|
||||
try_extract_error_name("KinoviError::RequestError(asdf)"),
|
||||
Some("KinoviError::RequestError".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_error_with_nested_errors() {
|
||||
// Should only grab the outermost error name.
|
||||
assert_eq!(
|
||||
try_extract_error_name(
|
||||
"ComplexError::SomeError(SomeError::SomeVariant(SomeInnerError::SomeInnerVariant))"
|
||||
),
|
||||
Some("ComplexError::SomeError".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_error_with_struct_containing_nested() {
|
||||
assert_eq!(
|
||||
try_extract_error_name(
|
||||
"WrappedError::Variant { inner: SomeInnerError::SomeInnerVariant }"
|
||||
),
|
||||
Some("WrappedError::Variant".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_match_for_lowercase_string() {
|
||||
assert_eq!(
|
||||
try_extract_error_name("just a plain error message with no type name"),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_match_for_empty_string() {
|
||||
assert_eq!(try_extract_error_name(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_word_camel_case() {
|
||||
// A single CamelCase word like "Error" should not match because
|
||||
// the regex requires at least two segments (e.g. "Ab" = A + b).
|
||||
// "Error" = E + rror — "rror" has 4 lowercase chars which is fine.
|
||||
assert_eq!(
|
||||
try_extract_error_name("Error"),
|
||||
Some("Error".to_string()),
|
||||
);
|
||||
}
|
||||
|
||||
// --- try_error_title tests (with real error types) ---
|
||||
|
||||
#[test]
|
||||
fn title_from_io_error() {
|
||||
let err: Box<dyn std::error::Error> = Box::new(
|
||||
std::io::Error::new(std::io::ErrorKind::NotFound, "file not found"),
|
||||
);
|
||||
// io::Error Debug looks like: `Custom { kind: NotFound, error: "file not found" }`
|
||||
// "Custom" doesn't match CamelCase with 2+ segments... but we get what we get.
|
||||
let title = try_error_title(err.as_ref());
|
||||
// The exact output depends on Debug formatting. Just verify it doesn't panic.
|
||||
// Custom has one uppercase + lowercase sequence = "Custom" which matches [A-Z][0-9a-z]+
|
||||
assert!(title.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_from_serde_json_error() {
|
||||
let err: serde_json::Error = serde_json::from_str::<String>("not json").unwrap_err();
|
||||
let title = try_error_title(&err);
|
||||
// serde_json::Error Debug typically starts with "Error(...)"
|
||||
assert!(title.is_some());
|
||||
}
|
||||
|
||||
/// A simple custom error for testing.
|
||||
#[derive(Debug)]
|
||||
enum TestAppError {
|
||||
DatabaseTimeout,
|
||||
InvalidInput(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for TestAppError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for TestAppError {}
|
||||
|
||||
#[test]
|
||||
fn title_from_custom_unit_variant() {
|
||||
let err = TestAppError::DatabaseTimeout;
|
||||
let title = try_error_title(&err);
|
||||
assert_eq!(title, Some("DatabaseTimeout".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn title_from_custom_tuple_variant() {
|
||||
let err = TestAppError::InvalidInput("bad data".to_string());
|
||||
let title = try_error_title(&err);
|
||||
assert_eq!(title, Some("InvalidInput".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user