Skip to content

Commit

Permalink
editoast, front: forbid zero-length paths in pathfinding endpoint
Browse files Browse the repository at this point in the history
    - 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 <[email protected]>
  • Loading branch information
Sh099078 committed Dec 3, 2024
1 parent c98d385 commit 9b5ea0c
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 3 deletions.
64 changes: 63 additions & 1 deletion editoast/src/views/path/pathfinding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ editoast_common::schemas! {

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

impl <'de> Deserialize<'de> for PathfindingInput {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de> {
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Internal {
rolling_stock_loading_gauge: LoadingGaugeType,
rolling_stock_is_thermal: bool,
rolling_stock_supported_electrifications: Vec<String>,
rolling_stock_supported_signaling_systems: Vec<String>,
path_items: Vec<PathItemLocation>,
rolling_stock_maximum_speed: OrderedFloat<f64>,
rolling_stock_length: OrderedFloat<f64>,
}
let Internal {
rolling_stock_loading_gauge,
rolling_stock_is_thermal,
rolling_stock_supported_electrifications,
rolling_stock_supported_signaling_systems,
path_items,
rolling_stock_maximum_speed,
rolling_stock_length,
} = Internal::deserialize(deserializer)?;
if path_items.first() == path_items.last() {
return Err(serde::de::Error::custom("First and last path items cannot be the same"));
}
Ok(PathfindingInput {
rolling_stock_loading_gauge,
rolling_stock_is_thermal,
rolling_stock_supported_electrifications,
rolling_stock_supported_signaling_systems,
path_items,
rolling_stock_maximum_speed,
rolling_stock_length,
})
}
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum PathfindingResult {
Expand Down Expand Up @@ -448,6 +487,29 @@ pub mod tests {
use crate::views::path::pathfinding::PathfindingResult;
use crate::views::test_app::TestAppBuilder;

#[rstest]
async fn pathfinding_with_same_origin_and_destination_fails() {
let app = TestAppBuilder::default_app();
let db_pool = app.db_pool();
let small_infra = create_small_infra(&mut db_pool.get_ok()).await;

let request = app
.post(format!("/infra/{}/pathfinding/blocks", small_infra.id).as_str())
.json(&json!({
"path_items":[
{"trigram":"WS","secondary_code":"BV"},
{"trigram":"WS","secondary_code":"BV"},
],
"rolling_stock_is_thermal":true,
"rolling_stock_loading_gauge":"G1",
"rolling_stock_supported_electrifications":[],
"rolling_stock_supported_signaling_systems":["BAL","BAPR"],
"rolling_stock_maximum_speed":22.00,
"rolling_stock_length":26.00
}));
app.fetch(request).assert_status(StatusCode::UNPROCESSABLE_ENTITY);
}

#[rstest]
async fn pathfinding_with_invalid_path_items_returns_invalid_path_items() {
let app = TestAppBuilder::default_app();
Expand Down
3 changes: 2 additions & 1 deletion front/public/locales/en/stdcm.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
"noResults": "No path found",
"noScheduledPoint": "The calculation requires either the origin or destination schedule.",
"pathfindingFailed": "No path have been found for these waypoints.",
"requestFailed": "Calculation error for last minute train path"
"requestFailed": "Calculation error for last minute train path",
"sameOriginAndDestination": "The origin and destination cannot be the same."
},
"stdcmResults": "Results",
"stdcmSimulationReport": "Path simulation report",
Expand Down
3 changes: 2 additions & 1 deletion front/public/locales/fr/stdcm.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
"noResults": "Aucun sillon trouvé",
"noScheduledPoint": "Le calcul a besoin de l'horaire d'origine ou de celui de la destination.",
"pathfindingFailed": "Aucun chemin n'a été trouvé pour ces points de jalonnement.",
"requestFailed": "Erreur de calcul Sillon de dernière minute"
"requestFailed": "Erreur de calcul Sillon de dernière minute",
"sameOriginAndDestination": "L'origine et la destination ne peuvent pas être identiques."
},
"stdcmResults": "Résultats",
"stdcmSimulationReport": "Fiche simulation",
Expand Down
1 change: 1 addition & 0 deletions front/src/applications/stdcm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export enum StdcmConfigErrorTypes {
PATHFINDING_FAILED = 'pathfindingFailed',
BOTH_POINT_SCHEDULED = 'bothPointAreScheduled',
NO_SCHEDULED_POINT = 'noScheduledPoint',
SAME_ORIGIN_AND_DESTINATION = 'sameOriginAndDestination',
}

export type StdcmConfigErrors = {
Expand Down
4 changes: 4 additions & 0 deletions front/src/applications/stdcm/utils/checkStdcmConfigErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const checkStdcmConfigErrors = (
return { errorType: StdcmConfigErrorTypes.PATHFINDING_FAILED };
}

if (origin.name === destination.name && origin.ch === destination.ch) {
return { errorType: StdcmConfigErrorTypes.SAME_ORIGIN_AND_DESTINATION };
}

const areBothPointsASAP =
origin.arrivalType === ArrivalTimeTypes.ASAP &&
destination.arrivalType === ArrivalTimeTypes.ASAP;
Expand Down

0 comments on commit 9b5ea0c

Please sign in to comment.