Skip to content

Commit fce88db

Browse files
Baptiste PrevotCastavo
Baptiste Prevot
authored andcommitted
editoast: make rolling_stock.base_power_class nullable
1 parent dbf33b3 commit fce88db

File tree

11 files changed

+154
-28
lines changed

11 files changed

+154
-28
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- This file should undo anything in `up.sql`
2+
alter table rolling_stock
3+
drop constraint base_power_class_null_or_non_empty;
4+
5+
update
6+
rolling_stock
7+
set
8+
base_power_class = ''
9+
where
10+
base_power_class is null;
11+
12+
alter table rolling_stock alter column base_power_class set not null;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
-- Your SQL goes here
2+
alter table rolling_stock alter column base_power_class drop not null;
3+
4+
update
5+
rolling_stock
6+
set
7+
base_power_class = null
8+
where
9+
base_power_class = '';
10+
11+
alter table rolling_stock
12+
add constraint base_power_class_null_or_non_empty check (base_power_class is null
13+
or length(base_power_class) > 0);

editoast/openapi.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,7 @@ components:
11871187
properties:
11881188
base_power_class:
11891189
example: '5'
1190+
nullable: true
11901191
type: string
11911192
comfort_acceleration:
11921193
format: float

editoast/openapi_legacy.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -3096,6 +3096,7 @@ components:
30963096
- "FR3.3/GB/G2"
30973097
- "GLOTT"
30983098
base_power_class:
3099+
nullable: true
30993100
type: string
31003101
example: "5"
31013102
power_restrictions:

editoast/src/models/rolling_stock/light_rolling_stock.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub struct LightRollingStockModel {
3333
comfort_acceleration: f64,
3434
gamma: DieselJson<Gamma>,
3535
inertia_coefficient: f64,
36-
base_power_class: String,
36+
base_power_class: Option<String>,
3737
features: Vec<Option<String>>,
3838
mass: f64,
3939
rolling_resistance: DieselJson<RollingResistance>,

editoast/src/models/rolling_stock/mod.rs

+50-18
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ pub use light_rolling_stock::LightRollingStockModel;
77
pub use rolling_stock_image::RollingStockSeparatedImageModel;
88
pub use rolling_stock_livery::RollingStockLiveryModel;
99

10-
use crate::error::Result;
10+
use crate::error::{InternalError, Result};
1111
use crate::models::rolling_stock::rolling_stock_livery::RollingStockLiveryMetadata;
12-
use crate::models::{Identifiable, TextArray, Update};
12+
use crate::models::{Create, Identifiable, TextArray, Update};
1313
use crate::schema::rolling_stock::{
1414
EffortCurves, EnergySource, Gamma, RollingResistance, RollingStock, RollingStockCommon,
1515
RollingStockMetadata, RollingStockWithLiveries,
@@ -19,8 +19,8 @@ use crate::views::rolling_stocks::RollingStockError;
1919
use crate::DbPool;
2020
use actix_web::web::Data;
2121
use derivative::Derivative;
22-
use diesel::result::Error as DieselError;
23-
use diesel::{update, ExpressionMethods, QueryDsl, SelectableHelper};
22+
use diesel::result::{DatabaseErrorInformation, DatabaseErrorKind, Error as DieselError};
23+
use diesel::{insert_into, update, ExpressionMethods, QueryDsl, SelectableHelper};
2424
use diesel_async::{AsyncPgConnection as PgConnection, RunQueryDsl};
2525
use diesel_json::Json as DieselJson;
2626
use editoast_derive::Model;
@@ -41,7 +41,7 @@ use serde_json::Value as JsonValue;
4141
)]
4242
#[derivative(Default, PartialEq)]
4343
#[model(table = "rolling_stock")]
44-
#[model(create, retrieve, delete)]
44+
#[model(retrieve, delete)]
4545
#[diesel(table_name = rolling_stock)]
4646
pub struct RollingStockModel {
4747
#[diesel(deserialize_as = i64)]
@@ -70,8 +70,8 @@ pub struct RollingStockModel {
7070
pub gamma: Option<DieselJson<Gamma>>,
7171
#[diesel(deserialize_as = f64)]
7272
pub inertia_coefficient: Option<f64>,
73-
#[diesel(deserialize_as = String)]
74-
pub base_power_class: Option<String>,
73+
#[diesel(deserialize_as = Option<String>)]
74+
pub base_power_class: Option<Option<String>>,
7575
#[diesel(deserialize_as = TextArray)]
7676
pub features: Option<Vec<String>>,
7777
#[diesel(deserialize_as = f64)]
@@ -136,6 +136,32 @@ impl RollingStockModel {
136136
}
137137
}
138138

139+
fn is_given_constraint(
140+
error_info: &(dyn DatabaseErrorInformation + Send + Sync),
141+
column_name: &str,
142+
) -> bool {
143+
error_info
144+
.constraint_name()
145+
.map(|name| name == column_name)
146+
.unwrap_or(false)
147+
}
148+
149+
fn map_diesel_error(e: DieselError, rs_name: String) -> InternalError {
150+
match e {
151+
DieselError::DatabaseError(DatabaseErrorKind::UniqueViolation, info)
152+
if is_given_constraint(info.as_ref(), "rolling_stock_name_key") =>
153+
{
154+
RollingStockError::NameAlreadyUsed { name: rs_name }.into()
155+
}
156+
DieselError::DatabaseError(DatabaseErrorKind::CheckViolation, info)
157+
if is_given_constraint(info.as_ref(), "base_power_class_null_or_non_empty") =>
158+
{
159+
RollingStockError::BasePowerClassEmpty.into()
160+
}
161+
e => e.into(),
162+
}
163+
}
164+
139165
#[async_trait]
140166
impl Update for RollingStockModel {
141167
async fn update_conn(
@@ -151,17 +177,23 @@ impl Update for RollingStockModel {
151177
.await
152178
{
153179
Ok(rs) => Ok(Some(rs)),
154-
Err(DieselError::NotFound) => {
155-
Err(RollingStockError::NotFound { rolling_stock_id }.into())
156-
}
157-
Err(DieselError::DatabaseError(
158-
diesel::result::DatabaseErrorKind::UniqueViolation,
159-
_,
160-
)) => Err(RollingStockError::NameAlreadyUsed {
161-
name: self.name.unwrap(),
162-
}
163-
.into()),
164-
Err(e) => Err(e.into()),
180+
Err(DieselError::NotFound) => Ok(None),
181+
Err(e) => Err(map_diesel_error(e, self.name.unwrap())),
182+
}
183+
}
184+
}
185+
186+
#[async_trait]
187+
impl Create for RollingStockModel {
188+
async fn create_conn(self, conn: &mut PgConnection) -> Result<Self> {
189+
use crate::tables::rolling_stock::dsl::*;
190+
match insert_into(rolling_stock)
191+
.values(&self)
192+
.get_result(conn)
193+
.await
194+
{
195+
Ok(rs) => Ok(rs),
196+
Err(e) => Err(map_diesel_error(e, self.name.unwrap())),
165197
}
166198
}
167199
}

editoast/src/schema/rolling_stock/light_rolling_stock.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ pub struct LightRollingStock {
2020
pub locked: bool,
2121
#[diesel(sql_type = Jsonb)]
2222
pub effort_curves: DieselJson<LightEffortCurves>,
23-
#[diesel(sql_type = Text)]
24-
pub base_power_class: String,
23+
#[diesel(sql_type = Nullable<Text>)]
24+
pub base_power_class: Option<String>,
2525
#[diesel(sql_type = Double)]
2626
pub length: f64,
2727
#[diesel(sql_type = Double)]

editoast/src/schema/rolling_stock/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub const ROLLING_STOCK_RAILJSON_VERSION: &str = "3.2";
1414
pub struct RollingStockCommon {
1515
pub name: String,
1616
pub effort_curves: EffortCurves,
17-
pub base_power_class: String,
17+
pub base_power_class: Option<String>,
1818
pub length: f64,
1919
pub max_speed: f64,
2020
pub startup_time: f64,

editoast/src/tables.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ diesel::table! {
431431
gamma -> Jsonb,
432432
inertia_coefficient -> Float8,
433433
#[max_length = 255]
434-
base_power_class -> Varchar,
434+
base_power_class -> Nullable<Varchar>,
435435
features -> Array<Nullable<Text>>,
436436
mass -> Float8,
437437
rolling_resistance -> Jsonb,

editoast/src/views/rolling_stocks/mod.rs

+71-4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ pub enum RollingStockError {
5353
rolling_stock_id: i64,
5454
usage: Vec<TrainScheduleScenarioStudyProject>,
5555
},
56+
#[error("Base power class is an empty string")]
57+
#[editoast_error(status = 400)]
58+
BasePowerClassEmpty,
5659
}
5760

5861
pub fn routes() -> impl HttpServiceFactory {
@@ -193,10 +196,13 @@ async fn update(
193196

194197
let rolling_stock = rolling_stock
195198
.update(db_pool.clone(), rolling_stock_id)
196-
.await?
197-
.unwrap();
198-
let rolling_stock_with_liveries = rolling_stock.with_liveries(db_pool).await?;
199-
Ok(Json(rolling_stock_with_liveries))
199+
.await?;
200+
if let Some(rolling_stock) = rolling_stock {
201+
let rolling_stock_with_liveries = rolling_stock.with_liveries(db_pool).await?;
202+
Ok(Json(rolling_stock_with_liveries))
203+
} else {
204+
Err(RollingStockError::NotFound { rolling_stock_id }.into())
205+
}
200206
}
201207

202208
#[derive(Deserialize)]
@@ -490,6 +496,7 @@ pub mod tests {
490496
use crate::views::tests::create_test_service;
491497
use crate::{assert_editoast_error_type, assert_status_and_read, DbPool};
492498
use actix_http::{Request, StatusCode};
499+
use actix_web::dev::ServiceResponse;
493500
use actix_web::http::header::ContentType;
494501
use actix_web::test::{call_service, read_body_json, TestRequest};
495502
use actix_web::web::Data;
@@ -550,6 +557,66 @@ pub mod tests {
550557
assert_eq!(get_response.status(), StatusCode::NOT_FOUND);
551558
}
552559

560+
async fn check_create_gave_400(db_pool: Data<DbPool>, response: ServiceResponse) {
561+
if response.status() == StatusCode::OK {
562+
let rolling_stock: RollingStock = read_body_json(response).await;
563+
let rolling_stock_id = rolling_stock.id;
564+
RollingStockModel::delete(db_pool.clone(), rolling_stock_id)
565+
.await
566+
.unwrap();
567+
panic!("Rolling stock created but should not have been");
568+
} else {
569+
assert_eq!(
570+
response.status(),
571+
StatusCode::BAD_REQUEST,
572+
"Here is the full response body {:?}",
573+
response.into_body()
574+
);
575+
}
576+
}
577+
578+
#[rstest]
579+
async fn create_rolling_stock_with_base_power_class_empty(db_pool: Data<DbPool>) {
580+
let app = create_test_service().await;
581+
let mut rolling_stock: RollingStockModel = get_fast_rolling_stock();
582+
583+
rolling_stock.base_power_class = Some(Some("".to_string()));
584+
585+
let post_response = call_service(
586+
&app,
587+
TestRequest::post()
588+
.uri("/rolling_stock")
589+
.set_json(&rolling_stock)
590+
.to_request(),
591+
)
592+
.await;
593+
594+
check_create_gave_400(db_pool, post_response).await;
595+
}
596+
597+
#[rstest]
598+
async fn create_rolling_stock_with_duplicate_name(
599+
db_pool: Data<DbPool>,
600+
#[future] fast_rolling_stock: TestFixture<RollingStockModel>,
601+
) {
602+
let fast_rolling_stock = fast_rolling_stock.await;
603+
let app = create_test_service().await;
604+
let mut rolling_stock: RollingStockModel = get_fast_rolling_stock();
605+
606+
rolling_stock.name = fast_rolling_stock.model.name.clone();
607+
608+
let post_response = call_service(
609+
&app,
610+
TestRequest::post()
611+
.uri("/rolling_stock")
612+
.set_json(&rolling_stock)
613+
.to_request(),
614+
)
615+
.await;
616+
617+
check_create_gave_400(db_pool, post_response).await;
618+
}
619+
553620
#[rstest]
554621
async fn update_and_delete_locked_rolling_stock_fails(db_pool: Data<DbPool>) {
555622
let app = create_test_service().await;

front/src/common/api/osrdEditoastApi.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1527,7 +1527,7 @@ export type EnergySource =
15271527
energy_source_type: 'Battery';
15281528
} & Battery);
15291529
export type RollingStockBase = {
1530-
base_power_class: string;
1530+
base_power_class: string | null;
15311531
comfort_acceleration: number;
15321532
electrical_power_startup_time: number | null;
15331533
energy_sources: EnergySource[];

0 commit comments

Comments
 (0)