Skip to content

Commit

Permalink
editoast: return list of missing locations in pathfinding
Browse files Browse the repository at this point in the history
Signed-off-by: hamz2a <atrari.hamza@gmail.com>
  • Loading branch information
hamz2a committed Oct 1, 2024
1 parent b504cb1 commit c8d96e7
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 43 deletions.
38 changes: 21 additions & 17 deletions editoast/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3868,7 +3868,7 @@ components:
- $ref: '#/components/schemas/EditoastRollingStockErrorRollingStockIsLocked'
- $ref: '#/components/schemas/EditoastRollingStockErrorRollingStockIsUsed'
- $ref: '#/components/schemas/EditoastSTDCMErrorInfraNotFound'
- $ref: '#/components/schemas/EditoastSTDCMErrorInvalidPathItem'
- $ref: '#/components/schemas/EditoastSTDCMErrorInvalidPathItems'
- $ref: '#/components/schemas/EditoastSTDCMErrorRollingStockNotFound'
- $ref: '#/components/schemas/EditoastSTDCMErrorTimetableNotFound'
- $ref: '#/components/schemas/EditoastScenarioErrorInfraNotFound'
Expand Down Expand Up @@ -4767,7 +4767,7 @@ components:
type: string
enum:
- editoast:stdcm_v2:InfraNotFound
EditoastSTDCMErrorInvalidPathItem:
EditoastSTDCMErrorInvalidPathItems:
type: object
required:
- type
Expand All @@ -4777,13 +4777,10 @@ components:
context:
type: object
required:
- index
- path_item
- items
properties:
index:
type: integer
path_item:
type: object
items:
type: array
message:
type: string
status:
Expand All @@ -4793,7 +4790,7 @@ components:
type:
type: string
enum:
- editoast:stdcm_v2:InvalidPathItem
- editoast:stdcm_v2:InvalidPathItems
EditoastSTDCMErrorRollingStockNotFound:
type: object
required:
Expand Down Expand Up @@ -7063,19 +7060,26 @@ components:
- incompatible_constraints
- type: object
required:
- index
- path_item
- items
- status
properties:
index:
type: integer
minimum: 0
path_item:
$ref: '#/components/schemas/PathItemLocation'
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_item
- invalid_path_items
- type: object
required:
- status
Expand Down
8 changes: 8 additions & 0 deletions editoast/src/core/mocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ impl<'a> StubResponseBuilder<'a> {
self
}

#[allow(unused)]
#[must_use = "call .finish() to register the stub request"]
pub fn json<T: Serialize>(mut self, body: T) -> Self {
let json_body = serde_json::to_string(&body).expect("Failed to serialize JSON");
self.body = Some(Body::from(json_body));
self
}

/// Builds the [StubResponse] and registers it into the [MockingClient]
pub fn finish(self) {
self.request_builder.finish_with_response(StubResponse {
Expand Down
24 changes: 15 additions & 9 deletions editoast/src/core/pathfinding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,37 @@ pub struct PathfindingRequest {
pub rolling_stock_length: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
pub struct OffsetRange {
start: u64,
end: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
pub struct IncompatibleOffsetRangeWithValue {
range: OffsetRange,
value: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
pub struct IncompatibleOffsetRange {
range: OffsetRange,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
pub struct IncompatibleConstraints {
incompatible_electrification_ranges: Vec<IncompatibleOffsetRangeWithValue>,
incompatible_gauge_ranges: Vec<IncompatibleOffsetRange>,
incompatible_signaling_system_ranges: Vec<IncompatibleOffsetRangeWithValue>,
}

#[derive(Serialize, Deserialize, Clone, Debug, ToSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)]
pub struct InvalidPathItem {
pub index: usize,
pub path_item: PathItemLocation,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum PathfindingResult {
Success(PathfindingResultSuccess),
Expand All @@ -84,9 +90,9 @@ pub enum PathfindingResult {
relaxed_constraints_path: Box<PathfindingResultSuccess>,
incompatible_constraints: Box<IncompatibleConstraints>,
},
InvalidPathItem {
index: usize,
path_item: PathItemLocation,
InvalidPathItems {
#[schema(inline)]
items: Vec<InvalidPathItem>,
},
NotEnoughPathItems,
RollingStockNotFound {
Expand All @@ -98,7 +104,7 @@ pub enum PathfindingResult {
}

/// A successful pathfinding result. This is also used for STDCM response.
#[derive(Serialize, Deserialize, Clone, Debug, ToSchema)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, ToSchema)]
pub struct PathfindingResultSuccess {
#[schema(inline)]
/// Path description as block ids
Expand Down
21 changes: 17 additions & 4 deletions editoast/src/views/path/path_item_cache.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::core::pathfinding::InvalidPathItem;
use crate::core::pathfinding::PathfindingResult;
use crate::error::Result;
use crate::models::TrackSectionModel;
Expand Down Expand Up @@ -87,6 +88,7 @@ impl PathItemCache {
path_items: &[&PathItemLocation],
) -> TrackOffsetResult {
let mut result: Vec<Vec<_>> = Vec::default();
let mut invalid_path_items = Vec::new();
for (index, &path_item) in path_items.iter().enumerate() {
let track_offsets = match path_item {
PathItemLocation::TrackOffset(track_offset) => {
Expand All @@ -96,10 +98,11 @@ impl PathItemCache {
match self.get_from_id(&operational_point.0) {
Some(op) => op.track_offset(),
None => {
return Err(PathfindingResult::InvalidPathItem {
invalid_path_items.push(InvalidPathItem {
index,
path_item: path_item.clone(),
});
continue;
}
}
}
Expand All @@ -113,10 +116,11 @@ impl PathItemCache {
.unwrap_or_default();
let ops = secondary_code_filter(secondary_code, ops);
if ops.is_empty() {
return Err(PathfindingResult::InvalidPathItem {
invalid_path_items.push(InvalidPathItem {
index,
path_item: path_item.clone(),
});
continue;
}
track_offsets_from_ops(&ops)
}
Expand All @@ -130,10 +134,11 @@ impl PathItemCache {
.unwrap_or_default();
let ops = secondary_code_filter(secondary_code, ops);
if ops.is_empty() {
return Err(PathfindingResult::InvalidPathItem {
invalid_path_items.push(InvalidPathItem {
index,
path_item: path_item.clone(),
});
continue;
}
track_offsets_from_ops(&ops)
}
Expand All @@ -142,15 +147,23 @@ impl PathItemCache {
// Check if tracks exist
for track_offset in &track_offsets {
if !self.track_exists(&track_offset.track.0) {
return Err(PathfindingResult::InvalidPathItem {
invalid_path_items.push(InvalidPathItem {
index,
path_item: path_item.clone(),
});
continue;
}
}

result.push(track_offsets);
}

if !invalid_path_items.is_empty() {
return Err(PathfindingResult::InvalidPathItems {
items: invalid_path_items,
});
}

Ok(result)
}
}
Expand Down
106 changes: 106 additions & 0 deletions editoast/src/views/path/pathfinding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,109 @@ fn path_input_hash(infra: i64, infra_version: &String, path_input: &PathfindingI
let hash_path_input = hasher.finish();
format!("pathfinding_{osrd_version}.{infra}.{infra_version}.{hash_path_input}")
}

#[cfg(test)]
pub mod tests {
use axum::http::StatusCode;
use editoast_models::DbConnectionPoolV2;
use editoast_schemas::train_schedule::PathItemLocation;
use pretty_assertions::assert_eq;
use rstest::rstest;
use serde_json::json;

use crate::core::mocking::MockingClient;
use crate::core::pathfinding::InvalidPathItem;
use crate::core::pathfinding::PathfindingResult;
use crate::core::pathfinding::PathfindingResultSuccess;
use crate::models::fixtures::create_small_infra;
use crate::views::test_app::TestAppBuilder;

#[rstest]
async fn pathfinding_with_invalid_path_items_returns_invalid_path_items() {
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":"NO_TRIGRAM","secondary_code":null},
{"trigram":"SWS","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
}));

let pathfinding_result: PathfindingResult =
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
}
}]
}
);
}

#[rstest]
async fn pathfinding_with_valid_path_items_returns_successful_result() {
let db_pool = DbConnectionPoolV2::for_tests();
let mut core = MockingClient::new();
core.stub("/v2/pathfinding/blocks")
.method(reqwest::Method::POST)
.response(StatusCode::OK)
.json(json!({
"blocks":[],
"routes": [],
"track_section_ranges": [],
"path_item_positions": [],
"length": 0,
"status": "success"
}))
.finish();
let app = TestAppBuilder::new()
.db_pool(db_pool.clone())
.core_client(core.into())
.build();
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":"SWS","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
}));

let pathfinding_result: PathfindingResult =
app.fetch(request).assert_status(StatusCode::OK).json_into();
assert_eq!(
pathfinding_result,
PathfindingResult::Success(PathfindingResultSuccess {
blocks: vec![],
routes: vec![],
track_section_ranges: vec![],
length: 0,
path_item_positions: vec![]
})
);
}
}
12 changes: 4 additions & 8 deletions editoast/src/views/timetable/stdcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use utoipa::IntoParams;
use utoipa::ToSchema;

use super::SelectionSettings;
use crate::core::pathfinding::InvalidPathItem;
use crate::core::pathfinding::PathfindingResult;
use crate::core::simulation::{RoutingRequirement, SimulationResponse, SpacingRequirement};
use crate::core::stdcm::STDCMResponse;
Expand Down Expand Up @@ -68,11 +69,8 @@ enum STDCMError {
TimetableNotFound { timetable_id: i64 },
#[error("Rolling stock {rolling_stock_id} does not exist")]
RollingStockNotFound { rolling_stock_id: i64 },
#[error("Path item {index} is invalid")]
InvalidPathItem {
index: usize,
path_item: PathItemLocation,
},
#[error("Path items are invalid")]
InvalidPathItems { items: Vec<InvalidPathItem> },
}

/// An STDCM request
Expand Down Expand Up @@ -585,9 +583,7 @@ 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::InvalidPathItem { index, path_item } => {
STDCMError::InvalidPathItem { index, path_item }
}
PathfindingResult::InvalidPathItems { items } => STDCMError::InvalidPathItems { items },
_ => panic!("Unexpected pathfinding result"),
})?;

Expand Down
2 changes: 1 addition & 1 deletion front/public/locales/en/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
},
"stdcm_v2": {
"InfraNotFound": "Infrastructure '{{infra_id}}' does not exist",
"InvalidPathItem": "Path item '{{index}}' is invalid",
"InvalidPathItems": "Invalid waypoint(s) {{items}}",
"RollingStockNotFound": "Rolling stock '{{rolling_stock_id}}' does not exist",
"TimetableNotFound": "Timetable '{{timetable_id}}' does not exist"
},
Expand Down
2 changes: 1 addition & 1 deletion front/public/locales/fr/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
},
"stdcm_v2": {
"InfraNotFound": "Infrastructure '{{infra_id}}' non trouvée",
"InvalidPathItem": "Élément '{{index}}' du chemin non valide",
"InvalidPathItems": "Point(s) de passage {{items}} invalide(s)",
"RollingStockNotFound": "Matériel roulant '{{rolling_stock_id}}' non trouvé",
"TimetableNotFound": "Grille horaire '{{timetable_id}}' non trouvée"
},
Expand Down
Loading

0 comments on commit c8d96e7

Please sign in to comment.