Skip to content

Commit

Permalink
editoast: change Model trait Create to use editoast_models::Error
Browse files Browse the repository at this point in the history
Signed-off-by: Leo Valais <[email protected]>
  • Loading branch information
leovalais committed Dec 26, 2024
1 parent 588ed82 commit 4fe6913
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 38 deletions.
1 change: 1 addition & 0 deletions editoast/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion editoast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ postgres-openssl = "0.5.0"
pretty_assertions = "1.4.1"
rand = "0.8.5"
rangemap = "1.5.1"
regex = "1.11.1"
# 0.12.0 to 0.12.4 have weird timeout issues https://github.com/seanmonstar/reqwest/issues/2283
# This bug was introduced between 0.12.0 and 0.12.3.
reqwest = { version = "0.11.27", features = ["json"] }
Expand Down Expand Up @@ -162,7 +163,7 @@ redis = { version = "0.27", default-features = false, features = [
"tokio-comp",
"tokio-native-tls-comp",
] }
regex = "1.11.1"
regex.workspace = true
reqwest.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion editoast/editoast_derive/src/model/codegen/create_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl ToTokens for CreateImpl {
async fn create(
self,
conn: &mut editoast_models::DbConnection,
) -> crate::error::Result<#model> {
) -> std::result::Result<#model, editoast_models::model::Error> {
use diesel_async::RunQueryDsl;
use #table_mod::dsl;
use std::ops::DerefMut;
Expand Down
1 change: 1 addition & 0 deletions editoast/editoast_models/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ openssl.workspace = true
opentelemetry-semantic-conventions.workspace = true
postgis_diesel.workspace = true
postgres-openssl.workspace = true
regex.workspace = true
thiserror.workspace = true
tokio.workspace = true
tokio-postgres.workspace = true
Expand Down
49 changes: 48 additions & 1 deletion editoast/editoast_models/src/model/error.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,58 @@
use std::sync::LazyLock;

use diesel::result::DatabaseErrorKind;
use regex::Regex;

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("unique constraint violation: \"{constraint}\"")]
UniqueViolation { constraint: String },
#[error("check constraint violation on relation \"{relation}\": \"{constraint}\"")]
CheckViolation {
relation: String,
constraint: String,
},
#[error(transparent)]
DatabaseError(#[from] crate::DatabaseError),
}

impl From<diesel::result::Error> for Error {
fn from(e: diesel::result::Error) -> Self {
Self::DatabaseError(e.into())
match &e {
diesel::result::Error::DatabaseError(DatabaseErrorKind::UniqueViolation, inner) => {
static RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r#"duplicate key value violates unique constraint "([0-9a-zA-Z_-]+)""#,
)
.unwrap()
});
if let Some(captures) = RE.captures((*inner).message()) {
Self::UniqueViolation {
constraint: captures.get(1).unwrap().as_str().to_string(),
}
} else {
tracing::error!(?RE, %e, "failed to parse PostgreSQL error message");
Self::DatabaseError(e.into())
}
}
diesel::result::Error::DatabaseError(DatabaseErrorKind::CheckViolation, inner) => {
static RE: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r#"new row for relation "([0-9a-zA-Z_-]+)" violates check constraint "([0-9a-zA-Z_-]+)""#,
)
.unwrap()
});
if let Some(captures) = RE.captures((*inner).message()) {
Self::CheckViolation {
relation: captures.get(1).unwrap().as_str().to_string(),
constraint: captures.get(2).unwrap().as_str().to_string(),
}
} else {
tracing::error!(?RE, %e, "failed to parse PostgreSQL error message");
Self::DatabaseError(e.into())
}
}
_ => Self::DatabaseError(e.into()),
}
}
}
20 changes: 20 additions & 0 deletions editoast/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4763,6 +4763,7 @@ components:
- $ref: '#/components/schemas/EditoastStudyErrorDatabase'
- $ref: '#/components/schemas/EditoastStudyErrorNotFound'
- $ref: '#/components/schemas/EditoastStudyErrorStartDateAfterEndDate'
- $ref: '#/components/schemas/EditoastTemporarySpeedLimitErrorDatabase'
- $ref: '#/components/schemas/EditoastTemporarySpeedLimitErrorNameAlreadyUsed'
- $ref: '#/components/schemas/EditoastTimetableErrorDatabase'
- $ref: '#/components/schemas/EditoastTimetableErrorInfraNotFound'
Expand Down Expand Up @@ -6102,6 +6103,25 @@ components:
type: string
enum:
- editoast:study:StartDateAfterEndDate
EditoastTemporarySpeedLimitErrorDatabase:
type: object
required:
- type
- status
- message
properties:
context:
type: object
message:
type: string
status:
type: integer
enum:
- 500
type:
type: string
enum:
- editoast:temporary_speed_limit:Database
EditoastTemporarySpeedLimitErrorNameAlreadyUsed:
type: object
required:
Expand Down
9 changes: 5 additions & 4 deletions editoast/src/models/prelude/create.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::fmt::Debug;

use editoast_models::model;
use editoast_models::DbConnection;

use crate::error::EditoastError;
Expand All @@ -13,17 +14,17 @@ use crate::error::Result;
pub trait Create<Row: Send>: Sized {
/// Creates a new row in the database with the values of the changeset and
/// returns the created model instance
async fn create(self, conn: &mut DbConnection) -> Result<Row>;
async fn create(self, conn: &mut DbConnection) -> std::result::Result<Row, model::Error>;

/// Just like [Create::create] but discards the error if any and returns `Err(fail())` instead
async fn create_or_fail<E: EditoastError, F: FnOnce() -> E + Send>(
async fn create_or_fail<E: From<model::Error>, F: FnOnce() -> E + Send>(
self,
conn: &'async_trait mut DbConnection,
fail: F,
) -> Result<Row> {
) -> std::result::Result<Row, E> {
match self.create(conn).await {
Ok(obj) => Ok(obj),
Err(_) => Err(fail().into()),
Err(_) => Err(fail()),
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions editoast/src/models/stdcm_search_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ use diesel::ExpressionMethods;
use diesel::QueryDsl;
use diesel_async::RunQueryDsl;
use editoast_derive::Model;
use editoast_models::model;
use editoast_models::DbConnection;
use serde::Serialize;
use std::ops::DerefMut;
use std::result::Result;
use utoipa::ToSchema;

use crate::error::Result;
use crate::models::prelude::{Create, Row};
use crate::models::prelude::*;

#[cfg(test)]
use serde::Deserialize;
Expand Down Expand Up @@ -47,7 +48,7 @@ impl StdcmSearchEnvironment {
.ok()
}

pub async fn delete_all(conn: &mut DbConnection) -> Result<()> {
pub async fn delete_all(conn: &mut DbConnection) -> Result<(), model::Error> {
use editoast_models::tables::stdcm_search_environment::dsl::*;
diesel::delete(stdcm_search_environment)
.execute(conn.write().await.deref_mut())
Expand All @@ -57,7 +58,10 @@ impl StdcmSearchEnvironment {
}

impl StdcmSearchEnvironmentChangeset {
pub async fn overwrite(self, conn: &mut DbConnection) -> Result<StdcmSearchEnvironment> {
pub async fn overwrite(
self,
conn: &mut DbConnection,
) -> Result<StdcmSearchEnvironment, model::Error> {
StdcmSearchEnvironment::delete_all(conn).await?;
self.create(conn).await
}
Expand Down
35 changes: 33 additions & 2 deletions editoast/src/views/rolling_stock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub mod form;
pub mod light;
mod towed;

use editoast_models::model;
pub use form::RollingStockForm;

use std::io::Cursor;
Expand Down Expand Up @@ -147,7 +148,7 @@ pub enum RollingStockError {

#[error(transparent)]
#[editoast_error(status = 500)]
Database(#[from] editoast_models::model::Error),
Database(model::Error),
}

#[derive(Debug, Error)]
Expand Down Expand Up @@ -178,6 +179,7 @@ pub(crate) enum LiveryMultipartError {
},
}

// Still used to parse the error of `Update` and `Save`. Will be removed soon.
pub fn map_diesel_error(e: InternalError, name: impl AsRef<str>) -> InternalError {
if e.message
.contains(r#"duplicate key value violates unique constraint "rolling_stock_name_key""#)
Expand All @@ -190,6 +192,35 @@ pub fn map_diesel_error(e: InternalError, name: impl AsRef<str>) -> InternalErro
}
}

impl From<model::Error> for RollingStockError {
fn from(e: model::Error) -> Self {
match e {
model::Error::UniqueViolation { constraint }
if constraint == "rolling_stock_name_key" =>
{
Self::NameAlreadyUsed {
name: String::default(),
}
}
model::Error::CheckViolation { constraint, .. }
if constraint == "base_power_class_null_or_non_empty" =>
{
Self::BasePowerClassEmpty
}
e => Self::Database(e),
}
}
}

impl RollingStockError {
fn with_name(self, name: String) -> Self {
match self {
Self::NameAlreadyUsed { .. } => Self::NameAlreadyUsed { name },
e => e,
}
}
}

#[derive(IntoParams)]
#[allow(unused)]
pub struct RollingStockIdParam {
Expand Down Expand Up @@ -334,7 +365,7 @@ async fn create(
.version(0)
.create(conn)
.await
.map_err(|e| map_diesel_error(e, rolling_stock_name))?;
.map_err(|e| RollingStockError::from(e).with_name(rolling_stock_name))?;

Ok(Json(rolling_stock))
}
Expand Down
36 changes: 25 additions & 11 deletions editoast/src/views/temporary_speed_limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use axum::Extension;
use chrono::NaiveDateTime;
use chrono::Utc;
use editoast_derive::EditoastError;
use editoast_models::model;
use editoast_models::DbConnectionPoolV2;
use editoast_schemas::infra::DirectionalTrackRange;
use serde::de::Error as SerdeError;
Expand All @@ -12,7 +13,6 @@ use std::result::Result as StdResult;
use thiserror::Error;
use utoipa::ToSchema;

use crate::error::InternalError;
use crate::error::Result;
use crate::models::temporary_speed_limits::TemporarySpeedLimit;
use crate::models::temporary_speed_limits::TemporarySpeedLimitGroup;
Expand Down Expand Up @@ -111,18 +111,32 @@ enum TemporarySpeedLimitError {
#[error("Name '{name}' already used")]
#[editoast_error(status = 400)]
NameAlreadyUsed { name: String },
#[error(transparent)]
#[editoast_error(status = 500)]
Database(model::Error),
}

fn map_diesel_error(e: InternalError, name: impl AsRef<str>) -> InternalError {
if e.message.contains(
r#"duplicate key value violates unique constraint "temporary_speed_limit_group_name_key""#,
) {
TemporarySpeedLimitError::NameAlreadyUsed {
name: name.as_ref().to_string(),
impl From<model::Error> for TemporarySpeedLimitError {
fn from(e: model::Error) -> Self {
match e {
model::Error::UniqueViolation { constraint }
if constraint == "temporary_speed_limit_group_name_key" =>
{
Self::NameAlreadyUsed {
name: "unknown".to_string(),
}
}
e => Self::Database(e),
}
}
}

impl TemporarySpeedLimitError {
fn with_name(self, name: String) -> Self {
match self {
Self::NameAlreadyUsed { .. } => Self::NameAlreadyUsed { name },
e => e,
}
.into()
} else {
e
}
}

Expand Down Expand Up @@ -158,7 +172,7 @@ async fn create_temporary_speed_limit_group(
.creation_date(Utc::now().naive_utc())
.create(conn)
.await
.map_err(|e| map_diesel_error(e, speed_limit_group_name))?;
.map_err(|e| TemporarySpeedLimitError::from(e).with_name(speed_limit_group_name))?;

// Create the speed limits
let speed_limits_changesets = speed_limits
Expand Down
Loading

0 comments on commit 4fe6913

Please sign in to comment.