diff --git a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlockResponse.kt b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlockResponse.kt index 527d496f563..4f7e40a9b36 100644 --- a/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlockResponse.kt +++ b/core/src/main/kotlin/fr/sncf/osrd/api/api_v2/pathfinding/PathfindingBlockResponse.kt @@ -94,7 +94,7 @@ val polymorphicPathfindingResponseAdapter: PolymorphicJsonAdapterFactory = Moshi.Builder() diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index 3cfcd3b75ac..b5f8cfbe565 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -7391,6 +7391,39 @@ components: items: $ref: '#/components/schemas/TrackRange' description: List of track sections + PathfindingFailure: + oneOf: + - allOf: + - $ref: '#/components/schemas/PathfindingInputError' + - type: object + required: + - failed_status + properties: + failed_status: + type: string + enum: + - pathfinding_input_error + - allOf: + - $ref: '#/components/schemas/PathfindingNotFound' + - type: object + required: + - failed_status + properties: + failed_status: + type: string + enum: + - pathfinding_not_found + - type: object + required: + - core_error + - failed_status + properties: + core_error: + $ref: '#/components/schemas/InternalError' + failed_status: + type: string + enum: + - internal_error PathfindingInput: type: object description: |- @@ -7435,6 +7468,49 @@ components: items: type: string description: List of supported signaling systems + PathfindingInputError: + oneOf: + - type: object + required: + - items + - error_type + properties: + error_type: + type: string + enum: + - invalid_path_items + items: + type: array + items: + type: object + required: + - index + - path_item + properties: + index: + type: integer + minimum: 0 + path_item: + $ref: '#/components/schemas/PathItemLocation' + - type: object + required: + - error_type + properties: + error_type: + type: string + enum: + - not_enough_path_items + - type: object + required: + - rolling_stock_name + - error_type + properties: + error_type: + type: string + enum: + - rolling_stock_not_found + rolling_stock_name: + type: string PathfindingItem: type: object required: @@ -7452,55 +7528,22 @@ components: allOf: - $ref: '#/components/schemas/StepTimingData' nullable: true - PathfindingOutput: - type: object - required: - - track_ranges - - detectors - - switches_directions - properties: - detectors: - type: array - items: - type: string - maxLength: 255 - minLength: 1 - switches_directions: - type: object - additionalProperties: - type: string - maxLength: 255 - minLength: 1 - track_ranges: - type: array - items: - $ref: '#/components/schemas/DirectionalTrackRange' - PathfindingResult: + PathfindingNotFound: oneOf: - - allOf: - - $ref: '#/components/schemas/PathfindingResultSuccess' - - type: object - required: - - status - properties: - status: - type: string - enum: - - success - type: object required: - track_section_ranges - length - - status + - error_type properties: + error_type: + type: string + enum: + - not_found_in_blocks length: type: integer format: int64 minimum: 0 - status: - type: string - enum: - - not_found_in_blocks track_section_ranges: type: array items: @@ -7509,25 +7552,25 @@ components: required: - track_section_ranges - length - - status + - error_type properties: + error_type: + type: string + enum: + - not_found_in_routes length: type: integer format: int64 minimum: 0 - status: - type: string - enum: - - not_found_in_routes track_section_ranges: type: array items: $ref: '#/components/schemas/TrackRange' - type: object required: - - status + - error_type properties: - status: + error_type: type: string enum: - not_found_in_tracks @@ -7535,68 +7578,61 @@ components: required: - relaxed_constraints_path - incompatible_constraints - - status + - error_type properties: + error_type: + type: string + enum: + - incompatible_constraints incompatible_constraints: $ref: '#/components/schemas/IncompatibleConstraints' relaxed_constraints_path: $ref: '#/components/schemas/PathfindingResultSuccess' - status: - type: string - enum: - - incompatible_constraints - - type: object - required: - - items - - status - properties: + PathfindingOutput: + type: object + required: + - track_ranges + - detectors + - switches_directions + properties: + detectors: + type: array items: - type: array - items: - type: object - required: - - index - - path_item - properties: - index: - type: integer - minimum: 0 - path_item: - $ref: '#/components/schemas/PathItemLocation' - status: type: string - enum: - - invalid_path_items - - type: object - required: - - status - properties: - status: - type: string - enum: - - not_enough_path_items - - type: object - required: - - rolling_stock_name - - status - properties: - rolling_stock_name: - type: string - status: - type: string - enum: - - rolling_stock_not_found - - type: object - required: - - core_error - - status - properties: - core_error: - $ref: '#/components/schemas/InternalError' - status: + maxLength: 255 + minLength: 1 + switches_directions: + type: object + additionalProperties: type: string - enum: - - pathfinding_failed + maxLength: 255 + minLength: 1 + track_ranges: + type: array + items: + $ref: '#/components/schemas/DirectionalTrackRange' + PathfindingResult: + oneOf: + - allOf: + - $ref: '#/components/schemas/PathfindingResultSuccess' + - type: object + required: + - status + properties: + status: + type: string + enum: + - success + - allOf: + - $ref: '#/components/schemas/PathfindingFailure' + - type: object + required: + - status + properties: + status: + type: string + enum: + - failure PathfindingResultSuccess: type: object description: A successful pathfinding result. This is also used for STDCM response. @@ -9558,11 +9594,11 @@ components: - success - type: object required: - - pathfinding_result + - pathfinding_failed - status properties: - pathfinding_result: - $ref: '#/components/schemas/PathfindingResult' + pathfinding_failed: + $ref: '#/components/schemas/PathfindingFailure' status: type: string enum: @@ -9636,26 +9672,28 @@ components: format: int64 description: Travel time in ms minimum: 0 - - type: object - required: - - status - properties: - status: - type: string - enum: - - pathfinding_not_found + - allOf: + - $ref: '#/components/schemas/PathfindingNotFound' + - type: object + required: + - status + properties: + status: + type: string + enum: + - pathfinding_not_found - type: object description: An error has occured during pathfinding required: - - error_type + - core_error - status properties: - error_type: - type: string + core_error: + $ref: '#/components/schemas/InternalError' status: type: string enum: - - pathfinding_failed + - pathfinding_failure - type: object description: An error has occured during computing required: @@ -9668,18 +9706,16 @@ components: type: string enum: - simulation_failed - - type: object - description: Rolling stock not found - required: - - rolling_stock_name - - status - properties: - rolling_stock_name: - type: string - status: - type: string - enum: - - rolling_stock_not_found + - allOf: + - $ref: '#/components/schemas/PathfindingInputError' + - type: object + required: + - status + properties: + status: + type: string + enum: + - pathfinding_input_error Slope: type: object required: diff --git a/editoast/src/core/pathfinding.rs b/editoast/src/core/pathfinding.rs index 56982a9ab70..7acc24facad 100644 --- a/editoast/src/core/pathfinding.rs +++ b/editoast/src/core/pathfinding.rs @@ -14,10 +14,11 @@ editoast_common::schemas! { IncompatibleConstraints, IncompatibleOffsetRangeWithValue, IncompatibleOffsetRange, - PathfindingResult, PathfindingResultSuccess, OffsetRange, TrackRange, + PathfindingInputError, + PathfindingNotFound, } #[derive(Debug, Serialize)] @@ -73,9 +74,9 @@ pub struct InvalidPathItem { pub path_item: PathItemLocation, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(tag = "status", rename_all = "snake_case")] -pub enum PathfindingResult { +pub enum PathfindingCoreResult { Success(PathfindingResultSuccess), NotFoundInBlocks { track_section_ranges: Vec, @@ -91,14 +92,13 @@ pub enum PathfindingResult { incompatible_constraints: Box, }, InvalidPathItems { - #[schema(inline)] items: Vec, }, NotEnoughPathItems, RollingStockNotFound { rolling_stock_name: String, }, - PathfindingFailed { + InternalError { core_error: InternalError, }, } @@ -121,6 +121,39 @@ pub struct PathfindingResultSuccess { pub path_item_positions: Vec, } +// Enum for input-related errors +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)] +#[serde(tag = "error_type", rename_all = "snake_case")] +pub enum PathfindingInputError { + InvalidPathItems { + #[schema(inline)] + items: Vec, + }, + NotEnoughPathItems, + RollingStockNotFound { + rolling_stock_name: String, + }, +} + +// Enum for not-found results and incompatible constraints +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)] +#[serde(tag = "error_type", rename_all = "snake_case")] +pub enum PathfindingNotFound { + NotFoundInBlocks { + track_section_ranges: Vec, + length: u64, + }, + NotFoundInRoutes { + track_section_ranges: Vec, + length: u64, + }, + NotFoundInTracks, + IncompatibleConstraints { + relaxed_constraints_path: Box, + incompatible_constraints: Box, + }, +} + /// An oriented range on a track section. /// `begin` is always less than `end`. #[derive(Serialize, Deserialize, Clone, Debug, ToSchema, Hash, PartialEq, Eq)] @@ -216,7 +249,7 @@ impl TrackRangeOffset<'_> { } } -impl AsCoreRequest> for PathfindingRequest { +impl AsCoreRequest> for PathfindingRequest { const METHOD: reqwest::Method = reqwest::Method::POST; const URL_PATH: &'static str = "/v2/pathfinding/blocks"; diff --git a/editoast/src/core/simulation.rs b/editoast/src/core/simulation.rs index 0537c646914..ab8b0c75825 100644 --- a/editoast/src/core/simulation.rs +++ b/editoast/src/core/simulation.rs @@ -15,9 +15,9 @@ use serde::Serialize; use utoipa::ToSchema; use super::pathfinding::TrackRange; -use crate::core::pathfinding::PathfindingResult; use crate::core::{AsCoreRequest, Json}; use crate::error::InternalError; +use crate::views::path::pathfinding::PathfindingFailure; use derivative::Derivative; use editoast_schemas::primitives::Identifier; use std::hash::Hash; @@ -321,7 +321,7 @@ pub enum SimulationResponse { electrical_profiles: ElectricalProfiles, }, PathfindingFailed { - pathfinding_result: PathfindingResult, + pathfinding_failed: PathfindingFailure, }, SimulationFailed { core_error: InternalError, diff --git a/editoast/src/views/path/path_item_cache.rs b/editoast/src/views/path/path_item_cache.rs index b0b02293829..59b63c9514c 100644 --- a/editoast/src/views/path/path_item_cache.rs +++ b/editoast/src/views/path/path_item_cache.rs @@ -1,5 +1,5 @@ use crate::core::pathfinding::InvalidPathItem; -use crate::core::pathfinding::PathfindingResult; +use crate::core::pathfinding::PathfindingInputError; use crate::error::Result; use crate::models::TrackSectionModel; use crate::RetrieveBatchUnchecked; @@ -12,6 +12,9 @@ use editoast_schemas::train_schedule::PathItemLocation; use crate::models::OperationalPointModel; +use super::pathfinding::PathfindingFailure; +use super::pathfinding::PathfindingResult; + type TrackOffsetResult = std::result::Result>, PathfindingResult>; /// Gather information about several path items, factorizing db calls. @@ -159,9 +162,13 @@ impl PathItemCache { } if !invalid_path_items.is_empty() { - return Err(PathfindingResult::InvalidPathItems { - items: invalid_path_items, - }); + return Err(PathfindingResult::Failure( + PathfindingFailure::PathfindingInputError( + PathfindingInputError::InvalidPathItems { + items: invalid_path_items, + }, + ), + )); } Ok(result) diff --git a/editoast/src/views/path/pathfinding.rs b/editoast/src/views/path/pathfinding.rs index d0203bbd57c..03b6a95810b 100644 --- a/editoast/src/views/path/pathfinding.rs +++ b/editoast/src/views/path/pathfinding.rs @@ -13,14 +13,19 @@ use editoast_schemas::rolling_stock::LoadingGaugeType; use editoast_schemas::train_schedule::PathItemLocation; use ordered_float::OrderedFloat; use serde::Deserialize; +use serde::Serialize; use tracing::debug; use tracing::info; use utoipa::ToSchema; +use crate::core::pathfinding::PathfindingCoreResult; +use crate::core::pathfinding::PathfindingInputError; +use crate::core::pathfinding::PathfindingNotFound; use crate::core::pathfinding::PathfindingRequest; -use crate::core::pathfinding::PathfindingResult; +use crate::core::pathfinding::PathfindingResultSuccess; use crate::core::AsCoreRequest; use crate::core::CoreClient; +use crate::error::InternalError; use crate::error::Result; use crate::models::train_schedule::TrainSchedule; use crate::models::Infra; @@ -41,6 +46,8 @@ crate::routes! { editoast_common::schemas! { PathfindingInput, + PathfindingFailure, + PathfindingResult, } /// Path input is described by some rolling stock information @@ -66,6 +73,77 @@ struct PathfindingInput { rolling_stock_length: OrderedFloat, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)] +#[serde(tag = "status", rename_all = "snake_case")] +pub enum PathfindingResult { + Success(PathfindingResultSuccess), + Failure(PathfindingFailure), +} + +impl From for PathfindingResult { + fn from(core_result: PathfindingCoreResult) -> Self { + match core_result { + PathfindingCoreResult::Success(success) => PathfindingResult::Success(success), + PathfindingCoreResult::NotFoundInBlocks { + track_section_ranges, + length, + } => PathfindingResult::Failure(PathfindingFailure::PathfindingNotFound( + PathfindingNotFound::NotFoundInBlocks { + track_section_ranges, + length, + }, + )), + PathfindingCoreResult::NotFoundInRoutes { + track_section_ranges, + length, + } => PathfindingResult::Failure(PathfindingFailure::PathfindingNotFound( + PathfindingNotFound::NotFoundInRoutes { + track_section_ranges, + length, + }, + )), + PathfindingCoreResult::NotFoundInTracks => PathfindingResult::Failure( + PathfindingFailure::PathfindingNotFound(PathfindingNotFound::NotFoundInTracks), + ), + PathfindingCoreResult::IncompatibleConstraints { + relaxed_constraints_path, + incompatible_constraints, + } => PathfindingResult::Failure(PathfindingFailure::PathfindingNotFound( + PathfindingNotFound::IncompatibleConstraints { + relaxed_constraints_path, + incompatible_constraints, + }, + )), + PathfindingCoreResult::InvalidPathItems { items } => { + PathfindingResult::Failure(PathfindingFailure::PathfindingInputError( + PathfindingInputError::InvalidPathItems { items }, + )) + } + PathfindingCoreResult::NotEnoughPathItems => { + PathfindingResult::Failure(PathfindingFailure::PathfindingInputError( + PathfindingInputError::NotEnoughPathItems, + )) + } + PathfindingCoreResult::RollingStockNotFound { rolling_stock_name } => { + PathfindingResult::Failure(PathfindingFailure::PathfindingInputError( + PathfindingInputError::RollingStockNotFound { rolling_stock_name }, + )) + } + PathfindingCoreResult::InternalError { core_error } => { + PathfindingResult::Failure(PathfindingFailure::InternalError { core_error }) + } + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)] +#[serde(tag = "failed_status", rename_all = "snake_case")] +pub enum PathfindingFailure { + PathfindingInputError(PathfindingInputError), + PathfindingNotFound(PathfindingNotFound), + InternalError { core_error: InternalError }, +} + /// Compute a pathfinding #[utoipa::path( post, path = "", @@ -202,11 +280,13 @@ async fn pathfinding_blocks_batch( let path_index = pathfinding_requests_index[index]; let path = match path_result { Ok(path) => { - to_cache.push((&hashes[path_index], path.clone())); - path + to_cache.push((&hashes[path_index], path.clone().into())); + path.into() } // TODO: only make HTTP status code errors non-fatal - Err(core_error) => PathfindingResult::PathfindingFailed { core_error }, + Err(core_error) => { + PathfindingResult::Failure(PathfindingFailure::InternalError { core_error }) + } }; pathfinding_results[path_index] = Some(path); } @@ -224,7 +304,9 @@ fn build_pathfinding_request( ) -> std::result::Result { let path_items: Vec<_> = pathfinding_input.path_items.iter().collect(); if path_items.len() <= 1 { - return Err(PathfindingResult::NotEnoughPathItems); + return Err(PathfindingResult::Failure( + PathfindingFailure::PathfindingInputError(PathfindingInputError::NotEnoughPathItems), + )); } let track_offsets = path_item_cache.extract_location_from_path_items(&path_items)?; @@ -283,7 +365,12 @@ pub async fn pathfinding_from_train_batch( train_schedules: &[TrainSchedule], rolling_stocks: &HashMap, ) -> Result> { - let mut results = vec![PathfindingResult::NotEnoughPathItems; train_schedules.len()]; + let mut results = vec![ + PathfindingResult::Failure(PathfindingFailure::PathfindingInputError( + PathfindingInputError::NotEnoughPathItems + )); + train_schedules.len() + ]; let mut to_compute = vec![]; let mut to_compute_index = vec![]; for (index, train_schedule) in train_schedules.iter().enumerate() { @@ -291,7 +378,9 @@ pub async fn pathfinding_from_train_batch( let rolling_stock_name = &train_schedule.rolling_stock_name; let Some(rolling_stock) = rolling_stocks.get(rolling_stock_name).cloned() else { let rolling_stock_name = rolling_stock_name.clone(); - results[index] = PathfindingResult::RollingStockNotFound { rolling_stock_name }; + results[index] = PathfindingResult::Failure(PathfindingFailure::PathfindingInputError( + PathfindingInputError::RollingStockNotFound { rolling_stock_name }, + )); continue; }; @@ -349,9 +438,11 @@ pub mod tests { use crate::core::mocking::MockingClient; use crate::core::pathfinding::InvalidPathItem; - use crate::core::pathfinding::PathfindingResult; + use crate::core::pathfinding::PathfindingInputError; use crate::core::pathfinding::PathfindingResultSuccess; use crate::models::fixtures::create_small_infra; + use crate::views::path::pathfinding::PathfindingFailure; + use crate::views::path::pathfinding::PathfindingResult; use crate::views::test_app::TestAppBuilder; #[rstest] @@ -380,15 +471,17 @@ pub mod tests { app.fetch(request).assert_status(StatusCode::OK).json_into(); assert_eq!( pathfinding_result, - PathfindingResult::InvalidPathItems { - items: vec![InvalidPathItem { - index: 1, - path_item: PathItemLocation::OperationalPointDescription { - trigram: "NO_TRIGRAM".into(), - secondary_code: None - } - }] - } + PathfindingResult::Failure(PathfindingFailure::PathfindingInputError( + PathfindingInputError::InvalidPathItems { + items: vec![InvalidPathItem { + index: 1, + path_item: PathItemLocation::OperationalPointDescription { + trigram: "NO_TRIGRAM".into(), + secondary_code: None + } + }] + } + )) ); } diff --git a/editoast/src/views/timetable/stdcm.rs b/editoast/src/views/timetable/stdcm.rs index 9170507aff5..8c5c1f16c8b 100644 --- a/editoast/src/views/timetable/stdcm.rs +++ b/editoast/src/views/timetable/stdcm.rs @@ -27,7 +27,7 @@ use utoipa::ToSchema; use super::SelectionSettings; use crate::core::conflict_detection::TrainRequirements; use crate::core::pathfinding::InvalidPathItem; -use crate::core::pathfinding::PathfindingResult; +use crate::core::pathfinding::PathfindingInputError; use crate::core::simulation::PhysicsRollingStock; use crate::core::simulation::SimulationParameters; use crate::core::simulation::{RoutingRequirement, SimulationResponse, SpacingRequirement}; @@ -45,6 +45,8 @@ use crate::models::work_schedules::WorkSchedule; use crate::models::RollingStockModel; use crate::models::{Infra, List}; use crate::views::path::path_item_cache::PathItemCache; +use crate::views::path::pathfinding::PathfindingFailure; +use crate::views::path::pathfinding::PathfindingResult; use crate::views::train_schedule::train_simulation; use crate::views::train_schedule::train_simulation_batch; use crate::views::AuthorizationError; @@ -636,7 +638,9 @@ async fn parse_stdcm_steps( let track_offsets = path_item_cache .extract_location_from_path_items(&locations) .map_err(|path_res| match path_res { - PathfindingResult::InvalidPathItems { items } => STDCMError::InvalidPathItems { items }, + PathfindingResult::Failure(PathfindingFailure::PathfindingInputError( + PathfindingInputError::InvalidPathItems { items }, + )) => STDCMError::InvalidPathItems { items }, _ => panic!("Unexpected pathfinding result"), })?; diff --git a/editoast/src/views/train_schedule.rs b/editoast/src/views/train_schedule.rs index 28768098ecc..7059a6f25db 100644 --- a/editoast/src/views/train_schedule.rs +++ b/editoast/src/views/train_schedule.rs @@ -23,7 +23,8 @@ use utoipa::IntoParams; use utoipa::ToSchema; use crate::client::get_app_version; -use crate::core::pathfinding::PathfindingResult; +use crate::core::pathfinding::PathfindingInputError; +use crate::core::pathfinding::PathfindingNotFound; use crate::core::pathfinding::PathfindingResultSuccess; use crate::core::simulation::CompleteReportTrain; use crate::core::simulation::PhysicsRollingStock; @@ -39,12 +40,15 @@ use crate::core::simulation::SimulationScheduleItem; use crate::core::simulation::ZoneUpdate; use crate::core::AsCoreRequest; use crate::core::CoreClient; +use crate::error::InternalError; use crate::error::Result; use crate::models::infra::Infra; use crate::models::prelude::*; use crate::models::train_schedule::TrainSchedule; use crate::models::train_schedule::TrainScheduleChangeset; use crate::views::path::pathfinding::pathfinding_from_train; +use crate::views::path::pathfinding::PathfindingFailure; +use crate::views::path::pathfinding::PathfindingResult; use crate::views::path::pathfinding_from_train_batch; use crate::views::path::PathfindingError; use crate::views::AuthorizationError; @@ -446,9 +450,9 @@ pub async fn train_simulation_batch( }, path_item_positions, ), - _ => { + PathfindingResult::Failure(pathfinding_failed) => { simulation_results[index] = SimulationResponse::PathfindingFailed { - pathfinding_result: pathfinding.clone(), + pathfinding_failed: pathfinding_failed.clone(), }; continue; } @@ -637,13 +641,13 @@ enum SimulationSummaryResult { path_item_times_base: Vec, }, /// Pathfinding not found - PathfindingNotFound, + PathfindingNotFound(PathfindingNotFound), /// An error has occured during pathfinding - PathfindingFailed { error_type: String }, + PathfindingFailure { core_error: InternalError }, /// An error has occured during computing SimulationFailed { error_type: String }, - /// Rolling stock not found - RollingStockNotFound { rolling_stock_name: String }, + /// InputError + PathfindingInputError(PathfindingInputError), } /// Associate each train id with its simulation summary response @@ -721,17 +725,19 @@ async fn simulation_summary( path_item_times_base: base.path_item_times.clone(), } } - SimulationResponse::PathfindingFailed { pathfinding_result } => { - match pathfinding_result { - PathfindingResult::PathfindingFailed { core_error } => { - SimulationSummaryResult::PathfindingFailed { - error_type: core_error.get_type().into(), - } + SimulationResponse::PathfindingFailed { pathfinding_failed } => { + match pathfinding_failed { + PathfindingFailure::InternalError { core_error } => { + SimulationSummaryResult::PathfindingFailure { core_error } } - PathfindingResult::RollingStockNotFound { rolling_stock_name } => { - SimulationSummaryResult::RollingStockNotFound { rolling_stock_name } + + PathfindingFailure::PathfindingInputError(input_error) => { + SimulationSummaryResult::PathfindingInputError(input_error) + } + + PathfindingFailure::PathfindingNotFound(not_found) => { + SimulationSummaryResult::PathfindingNotFound(not_found) } - _ => SimulationSummaryResult::PathfindingNotFound, } } SimulationResponse::SimulationFailed { core_error } => { diff --git a/editoast/src/views/train_schedule/projection.rs b/editoast/src/views/train_schedule/projection.rs index ce6f75b721e..aacf59f81dc 100644 --- a/editoast/src/views/train_schedule/projection.rs +++ b/editoast/src/views/train_schedule/projection.rs @@ -20,7 +20,6 @@ use utoipa::ToSchema; use super::TrainScheduleError; use crate::client::get_app_version; -use crate::core::pathfinding::PathfindingResult; use crate::core::pathfinding::PathfindingResultSuccess; use crate::core::pathfinding::TrackRange; use crate::core::signal_projection::SignalUpdate; @@ -34,6 +33,7 @@ use crate::models::infra::Infra; use crate::models::train_schedule::TrainSchedule; use crate::models::Retrieve; use crate::models::RetrieveBatch; +use crate::views::path::pathfinding::PathfindingResult; use crate::views::path::projection::PathProjection; use crate::views::path::projection::TrackLocationFromPath; use crate::views::train_schedule::train_simulation_batch; diff --git a/front/public/locales/en/operationalStudies/manageTrainSchedule.json b/front/public/locales/en/operationalStudies/manageTrainSchedule.json index 8473efd4a7b..9af591733d2 100644 --- a/front/public/locales/en/operationalStudies/manageTrainSchedule.json +++ b/front/public/locales/en/operationalStudies/manageTrainSchedule.json @@ -104,7 +104,7 @@ "not_found_in_routes": "Missing route", "not_found_in_tracks": "Missing track", "not_enough_path_items": "Missing parameters for pathfinding: Departure, Arrival, Rolling stock", - "pathfinding_failed": "No path found", + "pathfinding_failure": "No path found", "rolling_stock_not_found": "Rolling stock not found" }, "powerRestriction": "Power restriction code", diff --git a/front/public/locales/en/operationalStudies/scenario.json b/front/public/locales/en/operationalStudies/scenario.json index e18d7a6f31d..46dffde9ded 100644 --- a/front/public/locales/en/operationalStudies/scenario.json +++ b/front/public/locales/en/operationalStudies/scenario.json @@ -53,10 +53,16 @@ "filterLabel": "Name, label", "importTrainSchedule": "Import", "invalid": { + "incompatible_constraints": "Incompatible constraints", + "invalid_path_items": "Invalid path items", + "not_enough_path_items": "Not enough path items", + "not_found_in_blocks": "Missing block", + "not_found_in_routes": "Missing route", + "not_found_in_tracks": "Missing track", + "pathfinding_failure": "Pathfinding failed", "pathfinding_not_found": "Pathfinding not found", - "pathfinding_failed": "Pathfinding failed", - "simulation_failed": "Simulation failed", - "rolling_stock_not_found": "RS not found" + "rolling_stock_not_found": "RS not found", + "simulation_failed": "Simulation failed" }, "invalidTrains": "Some trains are invalid", "noSpeedLimitTags": "Without code", diff --git a/front/public/locales/fr/operationalStudies/manageTrainSchedule.json b/front/public/locales/fr/operationalStudies/manageTrainSchedule.json index 0ddbb2c3f60..1388244c049 100644 --- a/front/public/locales/fr/operationalStudies/manageTrainSchedule.json +++ b/front/public/locales/fr/operationalStudies/manageTrainSchedule.json @@ -104,7 +104,7 @@ "not_found_in_routes": "Itinéraire manquant", "not_found_in_tracks": "Voie manquante", "not_enough_path_items": "Éléments manquants pour la recherche : Origine, Destination, Matériel roulant", - "pathfinding_failed": "Aucun chemin trouvé", + "pathfinding_failure": "Aucun chemin trouvé", "rolling_stock_not_found": "Matériel roulant non trouvé" }, "pleaseWait": "Veuillez patientez…", diff --git a/front/public/locales/fr/operationalStudies/scenario.json b/front/public/locales/fr/operationalStudies/scenario.json index 81d59b958b7..587252d0180 100644 --- a/front/public/locales/fr/operationalStudies/scenario.json +++ b/front/public/locales/fr/operationalStudies/scenario.json @@ -52,10 +52,16 @@ "filterLabel": "Nom, étiquette", "importTrainSchedule": "Importer", "invalid": { + "invalid_path_items": "Point(s) de passage(s) invalide(s)", + "incompatible_constraints": "Contraintes incompatibles", + "not_enough_path_items": "Point(s) de passage(s) manquant(s)", + "not_found_in_blocks": "Canton manquant", + "not_found_in_routes": "Itinéraire manquant", + "not_found_in_tracks": "Voie manquante", + "pathfinding_failure": "Calcul non abouti", "pathfinding_not_found": "Chemin introuvable", - "pathfinding_failed": "Calcul non abouti", - "simulation_failed": "Simulation impossible", - "rolling_stock_not_found": "MR non trouvé" + "rolling_stock_not_found": "MR non trouvé", + "simulation_failed": "Simulation impossible" }, "invalidTrains": "Certains trains sont invalides", "noSpeedLimitTags": "Sans code", diff --git a/front/src/applications/operationalStudies/helpers/formatTrainScheduleSummaries.ts b/front/src/applications/operationalStudies/helpers/formatTrainScheduleSummaries.ts index 066d1b31f73..ff1ce395acd 100644 --- a/front/src/applications/operationalStudies/helpers/formatTrainScheduleSummaries.ts +++ b/front/src/applications/operationalStudies/helpers/formatTrainScheduleSummaries.ts @@ -54,7 +54,11 @@ const formatTrainScheduleSummaries = ( duration: 0, pathLength: '', mechanicalEnergyConsumed: 0, - invalidReason: trainSummary.status, + invalidReason: + trainSummary.status === 'pathfinding_not_found' || + trainSummary.status === 'pathfinding_input_error' + ? trainSummary.error_type + : trainSummary.status, }; return { diff --git a/front/src/common/api/generatedEditoastApi.ts b/front/src/common/api/generatedEditoastApi.ts index 7c277598339..c1e5b827d50 100644 --- a/front/src/common/api/generatedEditoastApi.ts +++ b/front/src/common/api/generatedEditoastApi.ts @@ -2412,22 +2412,6 @@ export type PathfindingResultSuccess = { /** Path description as track ranges */ track_section_ranges: TrackRange[]; }; -export type OffsetRange = { - end: number; - start: number; -}; -export type IncompatibleOffsetRangeWithValue = { - range: OffsetRange; - value: string; -}; -export type IncompatibleOffsetRange = { - range: OffsetRange; -}; -export type IncompatibleConstraints = { - incompatible_electrification_ranges: IncompatibleOffsetRangeWithValue[]; - incompatible_gauge_ranges: IncompatibleOffsetRange[]; - incompatible_signaling_system_ranges: IncompatibleOffsetRangeWithValue[]; -}; export type TrackOffset = { /** Offset in mm */ offset: number; @@ -2449,46 +2433,74 @@ export type PathItemLocation = /** The [UIC](https://en.wikipedia.org/wiki/List_of_UIC_country_codes) code of an operational point */ uic: number; }; -export type PathfindingResult = - | (PathfindingResultSuccess & { - status: 'success'; - }) +export type PathfindingInputError = + | { + error_type: 'invalid_path_items'; + items: { + index: number; + path_item: PathItemLocation; + }[]; + } + | { + error_type: 'not_enough_path_items'; + } + | { + error_type: 'rolling_stock_not_found'; + rolling_stock_name: string; + }; +export type OffsetRange = { + end: number; + start: number; +}; +export type IncompatibleOffsetRangeWithValue = { + range: OffsetRange; + value: string; +}; +export type IncompatibleOffsetRange = { + range: OffsetRange; +}; +export type IncompatibleConstraints = { + incompatible_electrification_ranges: IncompatibleOffsetRangeWithValue[]; + incompatible_gauge_ranges: IncompatibleOffsetRange[]; + incompatible_signaling_system_ranges: IncompatibleOffsetRangeWithValue[]; +}; +export type PathfindingNotFound = | { + error_type: 'not_found_in_blocks'; length: number; - status: 'not_found_in_blocks'; track_section_ranges: TrackRange[]; } | { + error_type: 'not_found_in_routes'; length: number; - status: 'not_found_in_routes'; track_section_ranges: TrackRange[]; } | { - status: 'not_found_in_tracks'; + error_type: 'not_found_in_tracks'; } | { + error_type: 'incompatible_constraints'; incompatible_constraints: IncompatibleConstraints; relaxed_constraints_path: PathfindingResultSuccess; - status: 'incompatible_constraints'; - } - | { - items: { - index: number; - path_item: PathItemLocation; - }[]; - status: 'invalid_path_items'; - } - | { - status: 'not_enough_path_items'; - } - | { - rolling_stock_name: string; - status: 'rolling_stock_not_found'; - } + }; +export type PathfindingFailure = + | (PathfindingInputError & { + failed_status: 'pathfinding_input_error'; + }) + | (PathfindingNotFound & { + failed_status: 'pathfinding_not_found'; + }) | { core_error: InternalError; - status: 'pathfinding_failed'; + failed_status: 'internal_error'; }; +export type PathfindingResult = + | (PathfindingResultSuccess & { + status: 'success'; + }) + | (PathfindingFailure & { + status: 'failure'; + }); export type PathfindingInput = { /** List of waypoints given to the pathfinding */ path_items: PathItemLocation[]; @@ -3148,7 +3160,7 @@ export type SimulationResponse = status: 'success'; } | { - pathfinding_result: PathfindingResult; + pathfinding_failed: PathfindingFailure; status: 'pathfinding_failed'; } | { @@ -3312,21 +3324,20 @@ export type SimulationSummaryResult = /** Travel time in ms */ time: number; } - | { + | (PathfindingNotFound & { status: 'pathfinding_not_found'; - } + }) | { - error_type: string; - status: 'pathfinding_failed'; + core_error: InternalError; + status: 'pathfinding_failure'; } | { error_type: string; status: 'simulation_failed'; } - | { - rolling_stock_name: string; - status: 'rolling_stock_not_found'; - }; + | (PathfindingInputError & { + status: 'pathfinding_input_error'; + }); export type TrainScheduleForm = TrainScheduleBase & { /** Timetable attached to the train schedule */ timetable_id?: number | null; diff --git a/front/src/modules/pathfinding/hooks/usePathfinding.ts b/front/src/modules/pathfinding/hooks/usePathfinding.ts index 6444535a55d..243acd6bfde 100644 --- a/front/src/modules/pathfinding/hooks/usePathfinding.ts +++ b/front/src/modules/pathfinding/hooks/usePathfinding.ts @@ -225,11 +225,12 @@ export const usePathfinding = ( try { const pathfindingResult = await postPathfindingBlocks(pathfindingInput).unwrap(); + const incompatibleConstraintsCheck = + pathfindingResult.status === 'failure' && + pathfindingResult.failed_status === 'pathfinding_not_found' && + pathfindingResult.error_type === 'incompatible_constraints'; - if ( - pathfindingResult.status === 'success' || - pathfindingResult.status === 'incompatible_constraints' - ) { + if (pathfindingResult.status === 'success' || incompatibleConstraintsCheck) { const pathResult = pathfindingResult.status === 'success' ? pathfindingResult @@ -306,10 +307,9 @@ export const usePathfinding = ( allWaypoints, length: pathResult.length, trackSectionRanges: pathResult.track_section_ranges, - incompatibleConstraints: - pathfindingResult.status === 'incompatible_constraints' - ? pathfindingResult.incompatible_constraints - : undefined, + incompatibleConstraints: incompatibleConstraintsCheck + ? pathfindingResult.incompatible_constraints + : undefined, }); if (pathfindingResult.status === 'success') { @@ -317,14 +317,19 @@ export const usePathfinding = ( } else { pathfindingDispatch({ type: 'PATHFINDING_INCOMPATIBLE_CONSTRAINTS', - message: `pathfindingErrors.${pathfindingResult.status}`, + message: `pathfindingErrors.${pathfindingResult.error_type}`, }); } } + } else if (pathfindingResult.failed_status === 'internal_error') { + pathfindingDispatch({ + type: 'PATHFINDING_ERROR', + message: `pathfindingErrors.${pathfindingResult.core_error.message}`, + }); } else { pathfindingDispatch({ type: 'PATHFINDING_ERROR', - message: `pathfindingErrors.${pathfindingResult.status}`, + message: `pathfindingErrors.${pathfindingResult.error_type}`, }); } } catch (e) { diff --git a/front/src/modules/trainschedule/components/Timetable/consts.ts b/front/src/modules/trainschedule/components/Timetable/consts.ts index a6342a48542..c061615cd31 100644 --- a/front/src/modules/trainschedule/components/Timetable/consts.ts +++ b/front/src/modules/trainschedule/components/Timetable/consts.ts @@ -1,14 +1,5 @@ -import type { InvalidReason } from './types'; - -export const specialCodeDictionary: { [key: string]: string } = { +const specialCodeDictionary: { [key: string]: string } = { '': 'NO CODE', }; -export const invalidTrainValues: { - [key in InvalidReason]: InvalidReason; -} = { - pathfinding_not_found: 'pathfinding_not_found', - pathfinding_failed: 'pathfinding_failed', - rolling_stock_not_found: 'rolling_stock_not_found', - simulation_failed: 'simulation_failed', -}; +export default specialCodeDictionary; diff --git a/front/src/modules/trainschedule/components/Timetable/types.ts b/front/src/modules/trainschedule/components/Timetable/types.ts index a34a62a12ed..d2d47cc6836 100644 --- a/front/src/modules/trainschedule/components/Timetable/types.ts +++ b/front/src/modules/trainschedule/components/Timetable/types.ts @@ -1,5 +1,7 @@ import type { LightRollingStockWithLiveries, + PathfindingInputError, + PathfindingNotFound, SimulationSummaryResult, } from 'common/api/osrdEditoastApi'; @@ -26,4 +28,7 @@ export type TrainScheduleWithDetails = { isValid: boolean; }; -export type InvalidReason = Exclude; +export type InvalidReason = + | Extract + | PathfindingNotFound['error_type'] + | PathfindingInputError['error_type']; diff --git a/front/src/modules/trainschedule/components/Timetable/utils.ts b/front/src/modules/trainschedule/components/Timetable/utils.ts index 08e2655b70c..4f41df7c916 100644 --- a/front/src/modules/trainschedule/components/Timetable/utils.ts +++ b/front/src/modules/trainschedule/components/Timetable/utils.ts @@ -1,4 +1,4 @@ -import { specialCodeDictionary } from './consts'; +import specialCodeDictionary from './consts'; import type { TrainScheduleWithDetails } from './types'; /** Filter train schedules by their names and labels */