Skip to content

Commit 7c7ecdd

Browse files
committed
editoast, front: forbid zero-length paths in pathfinding endpoint
- Editoast: return a 422 Unprocessable Content error code at deserialization when the endpoint `pathfinding/blocks` is called with the same origin and destination. - Front: add a new type of stdcm error to handle that case and update the stdcm inputs validation method. Signed-off-by: Loup Federico <16464925+Sh099078@users.noreply.github.com>
1 parent c98d385 commit 7c7ecdd

File tree

5 files changed

+72
-3
lines changed

5 files changed

+72
-3
lines changed

editoast/src/views/path/pathfinding.rs

+63-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ editoast_common::schemas! {
5252

5353
/// Path input is described by some rolling stock information
5454
/// and a list of path waypoints
55-
#[derive(Deserialize, Clone, Debug, Hash, ToSchema)]
55+
#[derive(Clone, Debug, Hash, ToSchema)]
5656
struct PathfindingInput {
5757
/// The loading gauge of the rolling stock
5858
rolling_stock_loading_gauge: LoadingGaugeType,
@@ -73,6 +73,45 @@ struct PathfindingInput {
7373
rolling_stock_length: OrderedFloat<f64>,
7474
}
7575

76+
impl <'de> Deserialize<'de> for PathfindingInput {
77+
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
78+
where
79+
D: serde::Deserializer<'de> {
80+
#[derive(Deserialize)]
81+
#[serde(deny_unknown_fields)]
82+
struct Internal {
83+
rolling_stock_loading_gauge: LoadingGaugeType,
84+
rolling_stock_is_thermal: bool,
85+
rolling_stock_supported_electrifications: Vec<String>,
86+
rolling_stock_supported_signaling_systems: Vec<String>,
87+
path_items: Vec<PathItemLocation>,
88+
rolling_stock_maximum_speed: OrderedFloat<f64>,
89+
rolling_stock_length: OrderedFloat<f64>,
90+
}
91+
let Internal {
92+
rolling_stock_loading_gauge,
93+
rolling_stock_is_thermal,
94+
rolling_stock_supported_electrifications,
95+
rolling_stock_supported_signaling_systems,
96+
path_items,
97+
rolling_stock_maximum_speed,
98+
rolling_stock_length,
99+
} = Internal::deserialize(deserializer)?;
100+
if path_items.get(path_items.len() - 1) == path_items.get(0) {
101+
return Err(serde::de::Error::custom("First and last path items cannot be the same"));
102+
}
103+
Ok(PathfindingInput {
104+
rolling_stock_loading_gauge,
105+
rolling_stock_is_thermal,
106+
rolling_stock_supported_electrifications,
107+
rolling_stock_supported_signaling_systems,
108+
path_items,
109+
rolling_stock_maximum_speed,
110+
rolling_stock_length,
111+
})
112+
}
113+
}
114+
76115
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)]
77116
#[serde(tag = "status", rename_all = "snake_case")]
78117
pub enum PathfindingResult {
@@ -448,6 +487,29 @@ pub mod tests {
448487
use crate::views::path::pathfinding::PathfindingResult;
449488
use crate::views::test_app::TestAppBuilder;
450489

490+
#[rstest]
491+
async fn pathfinding_with_same_origin_and_destination_fails() {
492+
let app = TestAppBuilder::default_app();
493+
let db_pool = app.db_pool();
494+
let small_infra = create_small_infra(&mut db_pool.get_ok()).await;
495+
496+
let request = app
497+
.post(format!("/infra/{}/pathfinding/blocks", small_infra.id).as_str())
498+
.json(&json!({
499+
"path_items":[
500+
{"trigram":"WS","secondary_code":"BV"},
501+
{"trigram":"WS","secondary_code":"BV"},
502+
],
503+
"rolling_stock_is_thermal":true,
504+
"rolling_stock_loading_gauge":"G1",
505+
"rolling_stock_supported_electrifications":[],
506+
"rolling_stock_supported_signaling_systems":["BAL","BAPR"],
507+
"rolling_stock_maximum_speed":22.00,
508+
"rolling_stock_length":26.00
509+
}));
510+
app.fetch(request).assert_status(StatusCode::UNPROCESSABLE_ENTITY);
511+
}
512+
451513
#[rstest]
452514
async fn pathfinding_with_invalid_path_items_returns_invalid_path_items() {
453515
let app = TestAppBuilder::default_app();

front/public/locales/en/stdcm.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@
9292
"noResults": "No path found",
9393
"noScheduledPoint": "The calculation requires either the origin or destination schedule.",
9494
"pathfindingFailed": "No path have been found for these waypoints.",
95-
"requestFailed": "Calculation error for last minute train path"
95+
"requestFailed": "Calculation error for last minute train path",
96+
"sameOriginAndDestination": "The origin and destination cannot be the same."
9697
},
9798
"stdcmResults": "Results",
9899
"stdcmSimulationReport": "Path simulation report",

front/public/locales/fr/stdcm.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@
9292
"noResults": "Aucun sillon trouvé",
9393
"noScheduledPoint": "Le calcul a besoin de l'horaire d'origine ou de celui de la destination.",
9494
"pathfindingFailed": "Aucun chemin n'a été trouvé pour ces points de jalonnement.",
95-
"requestFailed": "Erreur de calcul Sillon de dernière minute"
95+
"requestFailed": "Erreur de calcul Sillon de dernière minute",
96+
"sameOriginAndDestination": "L'origine et la destination ne peuvent pas être identiques."
9697
},
9798
"stdcmResults": "Résultats",
9899
"stdcmSimulationReport": "Fiche simulation",

front/src/applications/stdcm/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export enum StdcmConfigErrorTypes {
162162
PATHFINDING_FAILED = 'pathfindingFailed',
163163
BOTH_POINT_SCHEDULED = 'bothPointAreScheduled',
164164
NO_SCHEDULED_POINT = 'noScheduledPoint',
165+
SAME_ORIGIN_AND_DESTINATION = 'sameOriginAndDestination',
165166
}
166167

167168
export type StdcmConfigErrors = {

front/src/applications/stdcm/utils/checkStdcmConfigErrors.ts

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ const checkStdcmConfigErrors = (
2525
return { errorType: StdcmConfigErrorTypes.PATHFINDING_FAILED };
2626
}
2727

28+
if (origin.name === destination.name && origin.ch === destination.ch) {
29+
return { errorType: StdcmConfigErrorTypes.SAME_ORIGIN_AND_DESTINATION };
30+
}
31+
2832
const areBothPointsASAP =
2933
origin.arrivalType === ArrivalTimeTypes.ASAP &&
3034
destination.arrivalType === ArrivalTimeTypes.ASAP;

0 commit comments

Comments
 (0)