Every error response in the { error, code, details? } format (step 2)

Moves every remaining hand-built error response onto ApiError: ~300
sites across faces, image, video, files, memories, versions,
duplicates, favorites, libraries, auth, clip/unified search, prefs,
reels, tts, insight chat, persona chat, knowledge and personas.

- 500s: the body was the error's text (anyhow / diesel / {:?}); now
  ApiError::internal logs it and sends a generic message.
- Each 4xx/502/503 has a stable code and a message written for users;
  statuses are unchanged. Shared helpers per module (faces_unavailable,
  person_not_found, unknown_library, insight_not_found, ...).
- Pre-format top-level fields kept where clients read them: upload 409
  duplicate_of / content_hash / library_name.
- PersonaChatError converts into ApiError in one place.
- Multipart upload errors use the format (error::multipart_config).
- Guard test: no non-test code builds HttpResponse::<error>() by hand.

Closes #202

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cameron CordesandClaude Opus 5.5 committed 2026-11-01 13:29:13 -05:00
1 parent 1836f1b9ca
commit 431c3a7492
23 files changed
+894 -542

No files matched your search

+11 -7
View File
@@ -1,3 +1,4 @@
use crate::error::ApiError;
use actix_web::Responder;
use actix_web::{
HttpResponse,
@@ -40,20 +41,24 @@ async fn register<D: UserDao>(
) -> impl Responder {
// Validate password strength
if let Err(msg) = validate_password(&user.password) {
return HttpResponse::BadRequest().body(msg);
return ApiError::bad_request("weak_password", msg).into_response();
}
if !user.username.is_empty() && user.password == user.confirmation {
let mut dao = user_dao.lock().expect("Unable to get UserDao");
if dao.user_exists(&user.username) {
HttpResponse::BadRequest().finish()
ApiError::conflict("username_taken", "That username is taken").into_response()
} else if let Some(_user) = dao.create_user(&user.username, &user.password) {
HttpResponse::Ok().finish()
} else {
HttpResponse::InternalServerError().finish()
ApiError::internal("create_user failed").into_response()
}
} else {
HttpResponse::BadRequest().finish()
ApiError::bad_request(
"invalid_registration",
"Enter a username and the same password twice",
)
.into_response()
}
}
@@ -77,15 +82,14 @@ pub async fn login<D: UserDao>(
) {
Ok(t) => t,
Err(e) => {
error!("Failed to encode JWT: {}", e);
return HttpResponse::InternalServerError().finish();
return ApiError::internal(format!("Failed to encode JWT: {}", e)).into_response();
}
};
HttpResponse::Ok().json(Token { token: &token })
} else {
error!("Failed login attempt for user: '{}'", creds.username);
HttpResponse::Unauthorized().finish()
ApiError::unauthorized("invalid_credentials", "Wrong username or password").into_response()
}
}