Skip to content

Commit

Permalink
editoast: move train schedules margins to schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
Wadjetz committed Apr 10, 2024
1 parent 66c43e2 commit 3a03f98
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 106 deletions.
6 changes: 6 additions & 0 deletions editoast/editoast_schemas/src/train_schedule.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
mod margins;
pub use margins::MarginValue;
pub use margins::Margins;

mod allowance;
pub use allowance::Allowance;
pub use allowance::AllowanceDistribution;
Expand All @@ -10,6 +14,8 @@ mod rjs_power_restriction_range;
pub use rjs_power_restriction_range::RjsPowerRestrictionRange;

editoast_common::schemas! {
margins::schemas(),
// TODO TrainSchedule V1 (it will be removed)
allowance::schemas(),
rjs_power_restriction_range::schemas(),
}
111 changes: 111 additions & 0 deletions editoast/editoast_schemas/src/train_schedule/margins.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use std::str::FromStr;

use derivative::Derivative;
use editoast_common::NonBlankString;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

editoast_common::schemas! {
Margins,
MarginValue,
}

#[derive(Debug, Clone, Serialize, Derivative, ToSchema)]
#[serde(deny_unknown_fields)]
#[derivative(Default)]
pub struct Margins {
#[schema(inline)]
pub boundaries: Vec<NonBlankString>,
#[derivative(Default(value = "vec![MarginValue::None]"))]
/// The values of the margins. Must contains one more element than the boundaries
/// Can be a percentage `X%`, a time in minutes per kilometer `Xmin/km` or `none`
#[schema(value_type = Vec<String>, example = json!(["none", "5%", "2min/km"]))]
pub values: Vec<MarginValue>,
}

impl<'de> Deserialize<'de> for Margins {
fn deserialize<D>(deserializer: D) -> Result<Margins, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct InternalMargins {
boundaries: Vec<NonBlankString>,
values: Vec<MarginValue>,
}

let InternalMargins { boundaries, values } = InternalMargins::deserialize(deserializer)?;
if boundaries.len() + 1 != values.len() {
return Err(serde::de::Error::custom(
"The number of boudaries and values must be the same",
));
}
Ok(Margins { boundaries, values })
}
}

#[derive(Debug, Copy, Clone, Default, PartialEq, Derivative, ToSchema)]
#[derivative(Hash)]
pub enum MarginValue {
#[default]
None,
Percentage(#[derivative(Hash(hash_with = "editoast_common::hash_float::<3,_>"))] f64),
MinPerKm(#[derivative(Hash(hash_with = "editoast_common::hash_float::<3,_>"))] f64),
}

impl<'de> Deserialize<'de> for MarginValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
if value.to_lowercase() == "none" {
return Ok(Self::None);
}
if value.ends_with('%') {
let float_value = f64::from_str(value[0..value.len() - 1].trim()).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&value),
&"a valid float",
)
})?;
if float_value <= 0.0 {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&value),
&"a strictly positive number",
));
}
return Ok(Self::Percentage(float_value));
}
if value.ends_with("min/km") {
let float_value: f64 =
f64::from_str(value[0..value.len() - 6].trim()).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&value),
&"a valid float",
)
})?;
if float_value <= 0.0 {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&value),
&"a strictly positive float",
));
}
return Ok(Self::MinPerKm(float_value));
}
Err(serde::de::Error::custom("Margin type not recognized"))
}
}

impl Serialize for MarginValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
MarginValue::None => serializer.serialize_str("none"),
MarginValue::Percentage(value) => serializer.serialize_str(&format!("{}%", value)),
MarginValue::MinPerKm(value) => serializer.serialize_str(&format!("{}min/km", value)),
}
}
}
2 changes: 1 addition & 1 deletion editoast/src/core/v2/simulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ use chrono::Utc;
use editoast_schemas::rolling_stock::EffortCurves;
use editoast_schemas::rolling_stock::Gamma;
use editoast_schemas::rolling_stock::RollingResistance;
use editoast_schemas::train_schedule::MarginValue;
use serde::Deserialize;
use serde::Serialize;

use crate::core::{AsCoreRequest, Json};
use crate::schema::v2::trainschedule::MarginValue;
use crate::schema::v2::trainschedule::TrainScheduleOptions;
use crate::schema::v2::trainschedule::{Comfort, Distribution};
use crate::views::v2::path::TrackRange;
Expand Down
2 changes: 1 addition & 1 deletion editoast/src/modelsv2/train_schedule.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use chrono::DateTime;
use chrono::Utc;
use editoast_derive::ModelV2;
use editoast_schemas::train_schedule::Margins;

use crate::schema::v2::trainschedule::Comfort;
use crate::schema::v2::trainschedule::Distribution;
use crate::schema::v2::trainschedule::Margins;
use crate::schema::v2::trainschedule::PathItem;
use crate::schema::v2::trainschedule::PowerRestrictionItem;
use crate::schema::v2::trainschedule::ScheduleItem;
Expand Down
110 changes: 6 additions & 104 deletions editoast/src/schema/v2/trainschedule.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::hash::Hash;
use std::str::FromStr;

use chrono::DateTime;
use chrono::Utc;
use derivative::Derivative;
use editoast_common::NonBlankString;
use editoast_schemas::track_offset::TrackOffset;
use editoast_schemas::train_schedule::Margins;
use serde::de::Error as SerdeError;
use serde::Deserialize;
use serde::Serialize;
use strum::FromRepr;
use utoipa::ToSchema;

use editoast_common::Identifier;
use editoast_common::NonBlankString;
use editoast_common::PositiveDuration;

#[derive(Debug, Default, Clone, Serialize, ToSchema)]
Expand Down Expand Up @@ -231,106 +231,6 @@ pub struct PathItem {
pub location: PathItemLocation,
}

#[derive(Debug, Clone, Serialize, Derivative, ToSchema)]
#[serde(deny_unknown_fields)]
#[derivative(Default)]
pub struct Margins {
#[schema(inline)]
pub boundaries: Vec<NonBlankString>,
#[derivative(Default(value = "vec![MarginValue::None]"))]
/// The values of the margins. Must contains one more element than the boundaries
/// Can be a percentage `X%`, a time in minutes per kilometer `Xmin/km` or `none`
#[schema(value_type = Vec<String>, example = json!(["none", "5%", "2min/km"]))]
pub values: Vec<MarginValue>,
}

impl<'de> Deserialize<'de> for Margins {
fn deserialize<D>(deserializer: D) -> Result<Margins, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct InternalMargins {
boundaries: Vec<NonBlankString>,
values: Vec<MarginValue>,
}

let InternalMargins { boundaries, values } = InternalMargins::deserialize(deserializer)?;
if boundaries.len() + 1 != values.len() {
return Err(serde::de::Error::custom(
"The number of boudaries and values must be the same",
));
}
Ok(Margins { boundaries, values })
}
}

#[derive(Debug, Copy, Clone, Default, PartialEq, Derivative)]
#[derivative(Hash)]
pub enum MarginValue {
#[default]
None,
Percentage(#[derivative(Hash(hash_with = "editoast_common::hash_float::<3,_>"))] f64),
MinPerKm(#[derivative(Hash(hash_with = "editoast_common::hash_float::<3,_>"))] f64),
}

impl<'de> Deserialize<'de> for MarginValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
if value.to_lowercase() == "none" {
return Ok(Self::None);
}
if value.ends_with('%') {
let float_value = f64::from_str(value[0..value.len() - 1].trim()).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&value),
&"a valid float",
)
})?;
if float_value <= 0.0 {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&value),
&"a strictly positive number",
));
}
return Ok(Self::Percentage(float_value));
}
if value.ends_with("min/km") {
let float_value: f64 =
f64::from_str(value[0..value.len() - 6].trim()).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&value),
&"a valid float",
)
})?;
if float_value <= 0.0 {
return Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Str(&value),
&"a strictly positive float",
));
}
return Ok(Self::MinPerKm(float_value));
}
Err(serde::de::Error::custom("Margin type not recognized"))
}
}

impl Serialize for MarginValue {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
MarginValue::None => serializer.serialize_str("none"),
MarginValue::Percentage(value) => serializer.serialize_str(&format!("{}%", value)),
MarginValue::MinPerKm(value) => serializer.serialize_str(&format!("{}min/km", value)),
}
}
}

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, FromRepr, ToSchema, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Comfort {
Expand All @@ -351,11 +251,13 @@ pub enum Distribution {
#[cfg(test)]
mod tests {
use chrono::Duration;
use editoast_schemas::train_schedule::MarginValue;
use editoast_schemas::train_schedule::Margins;
use editoast_schemas::train_schedule::PathItemLocation;
use editoast_schemas::train_schedule::ScheduleItem;
use serde_json::from_str;
use serde_json::to_string;

use super::MarginValue;
use super::Margins;
use super::PathItem;
use super::PathItemLocation;
use crate::schema::v2::trainschedule::ScheduleItem;
Expand Down

0 comments on commit 3a03f98

Please sign in to comment.