Skip to main content

systemprompt_models/api/errors/
repository.rs

1//! The canonical `RepositoryError` → [`ApiError`] mapping.
2//!
3//! A missing entity answers 404, a repository-raised conflict or a unique,
4//! exclusion or foreign-key violation 409, an invalid argument or a not-null or
5//! check violation 400, and everything else 500. Constraint names, SQL and
6//! backend text stay in the logged source, never in the body.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use systemprompt_traits::RepositoryError;
12
13use super::ApiError;
14
15const CONFLICT_MESSAGE: &str = "The request conflicts with existing data";
16const CONSTRAINT_MESSAGE: &str = "The request violates a data constraint";
17const REPOSITORY_FAILURE: &str = "Repository operation failed";
18
19impl From<RepositoryError> for ApiError {
20    fn from(err: RepositoryError) -> Self {
21        match err {
22            RepositoryError::NotFound { .. } => Self::not_found(err.to_string()),
23            RepositoryError::Conflict { .. } => Self::conflict(err.to_string()),
24            RepositoryError::InvalidArgument { .. } => Self::bad_request(err.to_string()),
25            other => classify_failure(other),
26        }
27    }
28}
29
30fn classify_failure(err: RepositoryError) -> ApiError {
31    let constraint = match &err {
32        RepositoryError::Constraint { kind, .. } => Some(*kind),
33        _ => None,
34    };
35    match constraint {
36        Some(kind) if kind.is_conflict() => ApiError::conflict(CONFLICT_MESSAGE)
37            .with_error_key(kind.code())
38            .with_source(err),
39        Some(kind) => ApiError::bad_request(CONSTRAINT_MESSAGE)
40            .with_error_key(kind.code())
41            .with_source(err),
42        None => ApiError::internal(REPOSITORY_FAILURE, err),
43    }
44}