From 6e4a0350575454443085034dba604471aca0690b Mon Sep 17 00:00:00 2001 From: Baptiste Prevot Date: Fri, 20 Oct 2023 12:22:17 +0200 Subject: [PATCH] editoast add -on-path views to utoipa --- editoast/Cargo.lock | 2 + editoast/Cargo.toml | 6 +- editoast/openapi.yaml | 119 ++++++++---------- editoast/openapi_legacy.yaml | 110 ---------------- editoast/osrd_containers/Cargo.toml | 2 + .../osrd_containers/src/rangemap_utils.rs | 6 +- editoast/src/views/mod.rs | 35 ++++-- editoast/src/views/pathfinding/catenaries.rs | 29 ++++- .../views/pathfinding/electrical_profiles.rs | 31 +++-- editoast/src/views/pathfinding/mod.rs | 24 ++-- front/src/common/api/osrdEditoastApi.ts | 33 ++--- 11 files changed, 164 insertions(+), 233 deletions(-) diff --git a/editoast/Cargo.lock b/editoast/Cargo.lock index 02b703fddbe..f457c2974be 100644 --- a/editoast/Cargo.lock +++ b/editoast/Cargo.lock @@ -2348,6 +2348,8 @@ dependencies = [ "rangemap", "serde", "serde_derive", + "serde_json", + "utoipa", ] [[package]] diff --git a/editoast/Cargo.toml b/editoast/Cargo.toml index 893bfd6e083..d74c77aadfe 100644 --- a/editoast/Cargo.toml +++ b/editoast/Cargo.toml @@ -12,6 +12,8 @@ members = [".", "editoast_derive", "osrd_containers"] rangemap = "1.4.0" serde = "1.0.188" serde_derive = "1.0.188" +serde_json = "1.0.107" +utoipa = { version = "3.5.0", features = ["actix_extras", "chrono", "uuid"] } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -56,7 +58,7 @@ sentry = "0.31.7" sentry-actix = "0.31.7" serde.workspace = true serde_derive.workspace = true -serde_json = "1.0.107" +serde_json.workspace = true serde_yaml = "0.9.25" strum = "0.25.0" strum_macros = "0.25.2" @@ -75,7 +77,7 @@ reqwest = { version = "0.11.22", features = ["json"] } osm4routing = "0.5.8" osmpbfreader = "0.16.0" itertools = "0.11.0" -utoipa = { version = "3.5.0", features = ["actix_extras", "chrono", "uuid"] } +utoipa.workspace = true paste = "1.0.14" [dev-dependencies] diff --git a/editoast/openapi.yaml b/editoast/openapi.yaml index 2333e5a5575..4179eb5a50c 100644 --- a/editoast/openapi.yaml +++ b/editoast/openapi.yaml @@ -86,6 +86,23 @@ components: maxItems: 2 minItems: 2 type: array + CatenariesOnPathResponse: + description: |- + A list of ranges associated to catenary modes. When a profile overlapping another is found, + a warning is added to the list + properties: + catenary_ranges: + items: + $ref: '#/components/schemas/RangedValue' + type: array + warnings: + items: + $ref: '#/components/schemas/InternalError' + type: array + required: + - catenary_ranges + - warnings + type: object Catenary: description: energy source for a rolling stock representing a catenary properties: @@ -983,6 +1000,23 @@ components: - stop - code - handled + ProfilesOnPathResponse: + description: |- + A list of ranges associated to electrical profile values. When a profile overlapping another is found, + a warning is added to the list + properties: + electrical_profile_ranges: + items: + $ref: '#/components/schemas/RangedValue' + type: array + warnings: + items: + $ref: '#/components/schemas/InternalError' + type: array + required: + - electrical_profile_ranges + - warnings + type: object ProjectCreateRequest: properties: budget: @@ -1172,14 +1206,15 @@ components: - end_position - value RangedValue: + description: A struct to represent range maps in responses properties: begin: example: 0.0 - format: float + format: double type: number end: - example: 100.0 - format: float + example: 10.0 + format: double type: number value: example: '25000' @@ -3841,105 +3876,57 @@ paths: - pathfinding /pathfinding/{path_id}/catenaries/: get: + description: Retrieve the catenary modes along a path, as seen by the rolling stock specified parameters: - description: The path's id in: path name: path_id required: true schema: + format: int64 type: integer responses: '200': content: application/json: schema: - properties: - catenary_ranges: - items: - $ref: '#/components/schemas/RangedValue' - type: array - warnings: - items: - properties: - catenary_id: - type: string - overlapping_ranges: - items: - $ref: '#/components/schemas/TrackRange' - type: array - type: - enum: - - CatenaryOverlap - type: string - required: - - type - - catenary_id - - overlapping_ranges - type: object - type: array - required: - - catenary_ranges - - warnings - type: object - description: A list of ranges associated to catenary modes. When a catenary overlapping another is found, a warning is added to the list. - summary: Retrieve the modes of catenaries along a path + $ref: '#/components/schemas/CatenariesOnPathResponse' + description: '' + summary: Retrieve the catenary modes along a path, as seen by the rolling stock specified tags: - infra /pathfinding/{path_id}/electrical_profiles/: get: + description: Retrieve the electrical profiles along a path, as seen by the rolling stock specified parameters: - description: The path's id in: path name: path_id required: true schema: + format: int64 type: integer - - description: The electrical profile set's id - in: query - name: electrical_profile_set_id + - in: query + name: rolling_stock_id required: true schema: + format: int64 type: integer - - description: The id of the rolling stock you want to use - in: query - name: rolling stock id + - in: query + name: electrical_profile_set_id required: true schema: + format: int64 type: integer responses: '200': content: application/json: schema: - properties: - electrical_profile_ranges: - items: - $ref: '#/components/schemas/RangedValue' - type: array - warnings: - items: - properties: - overlapping_ranges: - items: - $ref: '#/components/schemas/TrackRange' - type: array - type: - enum: - - ElectricalProfilesOverlap - type: string - required: - - type - - overlapping_ranges - type: object - type: array - required: - - electrical_profile_ranges - - warnings - type: object - description: A list of ranges associated to catenary modes. When a catenary overlapping another is found, a warning is added to the list. + $ref: '#/components/schemas/ProfilesOnPathResponse' + description: '' summary: Retrieve the electrical profiles along a path, as seen by the rolling stock specified tags: - - infra - electrical_profiles /projects/: get: diff --git a/editoast/openapi_legacy.yaml b/editoast/openapi_legacy.yaml index 47c4cd82491..2fb326284d3 100644 --- a/editoast/openapi_legacy.yaml +++ b/editoast/openapi_legacy.yaml @@ -1878,100 +1878,6 @@ paths: schema: $ref: "#/components/schemas/ScenarioResult" - /pathfinding/{path_id}/catenaries/: - get: - tags: - - infra - summary: Retrieve the modes of catenaries along a path - parameters: - - in: path - name: path_id - schema: - type: integer - description: The path's id - required: true - responses: - 200: - description: A list of ranges associated to catenary modes. When a catenary overlapping another is found, a warning is added to the list. - content: - application/json: - schema: - type: object - properties: - catenary_ranges: - type: array - items: - $ref: "#/components/schemas/RangedValue" - warnings: - type: array - items: - type: object - properties: - type: - type: string - enum: ["CatenaryOverlap"] - catenary_id: - type: string - overlapping_ranges: - type: array - items: - $ref: "#/components/schemas/TrackRange" - required: [type, catenary_id, overlapping_ranges] - required: [catenary_ranges, warnings] - - - /pathfinding/{path_id}/electrical_profiles/: - get: - tags: - - infra - - electrical_profiles - summary: Retrieve the electrical profiles along a path, as seen by the rolling stock specified - parameters: - - in: path - name: path_id - schema: - type: integer - description: The path's id - required: true - - in: query - name: electrical_profile_set_id - schema: - type: integer - description: The electrical profile set's id - required: true - - in: query - name: rolling stock id - schema: - type: integer - description: The id of the rolling stock you want to use - required: true - responses: - 200: - description: A list of ranges associated to catenary modes. When a catenary overlapping another is found, a warning is added to the list. - content: - application/json: - schema: - type: object - properties: - electrical_profile_ranges: - type: array - items: - $ref: "#/components/schemas/RangedValue" - warnings: - type: array - items: - type: object - properties: - type: - type: string - enum: ["ElectricalProfilesOverlap"] - overlapping_ranges: - type: array - items: - $ref: "#/components/schemas/TrackRange" - required: [type, overlapping_ranges] - required: [electrical_profile_ranges, warnings] - /train_schedule/{id}/: get: tags: @@ -3272,22 +3178,6 @@ components: items: $ref: "#/components/schemas/TrackRange" - RangedValue: - type: object - properties: - begin: - type: number - format: float - example: 0.0 - end: - type: number - format: float - example: 100.0 - value: - type: string - example: "25000" - required: [begin, end, value] - SearchQuery: type: array minItems: 1 diff --git a/editoast/osrd_containers/Cargo.toml b/editoast/osrd_containers/Cargo.toml index d7fcf05ab1f..6016e505a16 100644 --- a/editoast/osrd_containers/Cargo.toml +++ b/editoast/osrd_containers/Cargo.toml @@ -9,3 +9,5 @@ edition = "2021" rangemap.workspace = true serde.workspace = true serde_derive.workspace = true +serde_json.workspace = true +utoipa.workspace = true diff --git a/editoast/osrd_containers/src/rangemap_utils.rs b/editoast/osrd_containers/src/rangemap_utils.rs index 553bd1da819..2ed5e13f233 100644 --- a/editoast/osrd_containers/src/rangemap_utils.rs +++ b/editoast/osrd_containers/src/rangemap_utils.rs @@ -2,6 +2,7 @@ use rangemap::RangeMap; use serde_derive::{Deserialize, Serialize}; use std::fmt::Debug; use std::ops::Range; +use utoipa::ToSchema; /// A struct to make f64 Ord, to use in RangeMap #[derive(Debug, PartialEq, Copy, Clone, Serialize)] @@ -93,10 +94,13 @@ pub fn extend_range_map( } /// A struct to represent range maps in responses -#[derive(Debug, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Deserialize, PartialEq, Serialize, ToSchema)] pub struct RangedValue { + #[schema(example = 0.0)] pub begin: f64, + #[schema(example = 10.0)] pub end: f64, + #[schema(example = "25000")] pub value: String, } diff --git a/editoast/src/views/mod.rs b/editoast/src/views/mod.rs index 313e17125f6..39d926a37cf 100644 --- a/editoast/src/views/mod.rs +++ b/editoast/src/views/mod.rs @@ -43,6 +43,7 @@ fn routes_v2() -> Routes { timetable::routes(), documents::routes(), sprites::routes(), + pathfinding::routes(), } routes() } @@ -56,7 +57,7 @@ pub fn routes() -> impl HttpServiceFactory { electrical_profiles::routes(), rolling_stocks::routes(), light_rolling_stocks::routes(), - pathfinding::routes(), + pathfinding::routes_v1(), train_schedule::routes(), stdcm::routes(), ] @@ -67,6 +68,7 @@ schemas! { Version, timetable::schemas(), documents::schemas(), + pathfinding::schemas(), } pub fn study_routes() -> impl HttpServiceFactory { @@ -230,18 +232,25 @@ mod tests { #[macro_export] macro_rules! assert_status_and_read { ($response: ident, $status: expr) => {{ - let (status, body): (_, serde_json::Value) = ( + let (status, body): (_, std::result::Result) = ( $response.status(), - actix_web::test::read_body_json($response).await, + actix_web::test::try_read_body_json($response).await, ); - let fmt_body = format!("{}", body); - assert_eq!(status, $status, "unexpected error response: {}", fmt_body); - match serde_json::from_value(body) { - Ok(response) => response, - Err(err) => panic!( - "cannot deserialize response because '{}': {}", - err, fmt_body - ), + if let std::result::Result::Ok(body) = body { + let fmt_body = format!("{}", body); + assert_eq!(status, $status, "unexpected error response: {}", fmt_body); + match serde_json::from_value(body) { + Ok(response) => response, + Err(err) => panic!( + "cannot deserialize response because '{}': {}", + err, fmt_body + ), + } + } else { + panic!( + "Cannot read response body: {:?}\nGot status code {}", + body, status + ) } }}; } @@ -254,7 +263,9 @@ mod tests { macro_rules! assert_editoast_error_type { ($response: ident, $error: expr) => {{ let expected_error: $crate::error::InternalError = $error.into(); - let payload: serde_json::Value = actix_web::test::read_body_json($response).await; + let payload: serde_json::Value = actix_web::test::try_read_body_json($response) + .await + .expect("cannot read response body"); let error_type = payload.get("type").expect("invalid error format"); assert_eq!(error_type, expected_error.get_type(), "error type mismatch"); }}; diff --git a/editoast/src/views/pathfinding/catenaries.rs b/editoast/src/views/pathfinding/catenaries.rs index 34fb463ee2d..8c6d0179768 100644 --- a/editoast/src/views/pathfinding/catenaries.rs +++ b/editoast/src/views/pathfinding/catenaries.rs @@ -6,8 +6,7 @@ use crate::views::pathfinding::path_rangemap::{make_path_range_map, TrackMap}; use crate::views::pathfinding::PathfindingError; use crate::DbPool; use actix_web::{ - dev::HttpServiceFactory, - get, services, + get, web::{Data, Json, Path}, }; use chashmap::CHashMap; @@ -16,9 +15,15 @@ use rangemap::RangeMap; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; +use utoipa::ToSchema; -pub fn routes() -> impl HttpServiceFactory { - services![catenaries_on_path,] +crate::routes! { + catenaries_on_path, +} + +crate::schemas! { + CatenariesOnPathResponse, + &osrd_containers::rangemap_utils::RangedValue, } /// Build a rangemap for each track section, giving the voltage for each range @@ -70,13 +75,25 @@ fn map_catenary_modes( (res, warnings) } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] +/// A list of ranges associated to catenary modes. When a profile overlapping another is found, +/// a warning is added to the list struct CatenariesOnPathResponse { catenary_ranges: Vec, warnings: Vec, } -#[get("/{path_id}/catenaries")] +#[utoipa::path( + tag = "infra", + params( + ("path_id" = i64, Path, description = "The path's id"), + ), + responses( + (status = 200, body = CatenariesOnPathResponse), + ) +)] +#[get("/pathfinding/{path_id}/catenaries")] +/// Retrieve the catenary modes along a path, as seen by the rolling stock specified async fn catenaries_on_path( pathfinding_id: Path, db_pool: Data, diff --git a/editoast/src/views/pathfinding/electrical_profiles.rs b/editoast/src/views/pathfinding/electrical_profiles.rs index d4f0339ca88..3e472d73c47 100644 --- a/editoast/src/views/pathfinding/electrical_profiles.rs +++ b/editoast/src/views/pathfinding/electrical_profiles.rs @@ -10,8 +10,7 @@ use crate::views::pathfinding::{ }; use crate::DbPool; use actix_web::{ - dev::HttpServiceFactory, - get, services, + get, web::{Data, Json, Path, Query}, }; use osrd_containers::rangemap_utils::RangedValue; @@ -19,9 +18,14 @@ use rangemap::RangeMap; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fmt::Debug; +use utoipa::{IntoParams, ToSchema}; -pub fn routes() -> impl HttpServiceFactory { - services![electrical_profiles_on_path,] +crate::routes! { + electrical_profiles_on_path +} + +crate::schemas! { + ProfilesOnPathResponse, } /// Build a rangemap for each track section, giving the electrical profile for each range @@ -59,19 +63,32 @@ fn map_electrical_profiles( (res, warnings) } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, IntoParams)] struct ProfilesOnPathQuery { rolling_stock_id: i64, electrical_profile_set_id: i64, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, ToSchema)] +/// A list of ranges associated to electrical profile values. When a profile overlapping another is found, +/// a warning is added to the list struct ProfilesOnPathResponse { electrical_profile_ranges: Vec, warnings: Vec, } -#[get("/{path_id}/electrical_profiles")] +#[utoipa::path( + tag = "electrical_profiles", + params( + ("path_id" = i64, Path, description = "The path's id"), + ProfilesOnPathQuery, + ), + responses( + (status = 200, body = ProfilesOnPathResponse), + ) +)] +#[get("/pathfinding/{path_id}/electrical_profiles")] +/// Retrieve the electrical profiles along a path, as seen by the rolling stock specified async fn electrical_profiles_on_path( pathfinding_id: Path, request: Query, diff --git a/editoast/src/views/pathfinding/mod.rs b/editoast/src/views/pathfinding/mod.rs index dbf79ad9ce9..62a5e4b4f11 100644 --- a/editoast/src/views/pathfinding/mod.rs +++ b/editoast/src/views/pathfinding/mod.rs @@ -73,16 +73,22 @@ enum PathfindingError { RollingStockNotFound { rolling_stock_id: i64 }, } +// FIXME: The following routes include "pathfinding" directly in their path, as to not conflict with the +// other routes. When the routes in mod.rs are moved to utoipa, the paths can be fixed. +crate::routes! { + catenaries::routes(), + electrical_profiles::routes(), +} + +crate::schemas! { + catenaries::schemas(), + electrical_profiles::schemas(), +} + /// Returns `/pathfinding` routes -pub fn routes() -> impl HttpServiceFactory { - web::scope("/pathfinding").service(( - get_pf, - del_pf, - create_pf, - update_pf, - catenaries::routes(), - electrical_profiles::routes(), - )) +/// TODO: This is not called `routes` because it should be integrated in the `routes!` macro +pub fn routes_v1() -> impl HttpServiceFactory { + web::scope("/pathfinding").service((get_pf, del_pf, create_pf, update_pf)) } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/front/src/common/api/osrdEditoastApi.ts b/front/src/common/api/osrdEditoastApi.ts index 6044ccada73..0fe247c9da2 100644 --- a/front/src/common/api/osrdEditoastApi.ts +++ b/front/src/common/api/osrdEditoastApi.ts @@ -340,11 +340,11 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/pathfinding/${queryArg.pathId}/electrical_profiles/`, params: { + rolling_stock_id: queryArg.rollingStockId, electrical_profile_set_id: queryArg.electricalProfileSetId, - 'rolling stock id': queryArg['rolling stock id'], }, }), - providesTags: ['infra', 'electrical_profiles'], + providesTags: ['electrical_profiles'], }), getProjects: build.query({ query: (queryArg) => ({ @@ -1000,33 +1000,18 @@ export type PutPathfindingByIdApiArg = { pathQuery: PathQuery; }; export type GetPathfindingByPathIdCatenariesApiResponse = - /** status 200 A list of ranges associated to catenary modes. When a catenary overlapping another is found, a warning is added to the list. */ { - catenary_ranges: RangedValue[]; - warnings: { - catenary_id: string; - overlapping_ranges: TrackRange[]; - type: 'CatenaryOverlap'; - }[]; - }; + /** status 200 */ CatenariesOnPathResponse; export type GetPathfindingByPathIdCatenariesApiArg = { /** The path's id */ pathId: number; }; export type GetPathfindingByPathIdElectricalProfilesApiResponse = - /** status 200 A list of ranges associated to catenary modes. When a catenary overlapping another is found, a warning is added to the list. */ { - electrical_profile_ranges: RangedValue[]; - warnings: { - overlapping_ranges: TrackRange[]; - type: 'ElectricalProfilesOverlap'; - }[]; - }; + /** status 200 */ ProfilesOnPathResponse; export type GetPathfindingByPathIdElectricalProfilesApiArg = { /** The path's id */ pathId: number; - /** The electrical profile set's id */ + rollingStockId: number; electricalProfileSetId: number; - /** The id of the rolling stock you want to use */ - 'rolling stock id': number; }; export type GetProjectsApiResponse = /** status 200 the project list */ { count?: number; @@ -1719,6 +1704,14 @@ export type RangedValue = { end: number; value: string; }; +export type CatenariesOnPathResponse = { + catenary_ranges: RangedValue[]; + warnings: InternalError[]; +}; +export type ProfilesOnPathResponse = { + electrical_profile_ranges: RangedValue[]; + warnings: InternalError[]; +}; export type ProjectResult = { budget: number; creation_date: string;