Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

editoast: remove 'validator' dependency #10273

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 18 additions & 42 deletions editoast/src/client/import_rolling_stock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@ use colored::Colorize as _;
use editoast_models::DbConnectionPoolV2;
use editoast_schemas::rolling_stock::RollingStock;
use editoast_schemas::rolling_stock::TowedRollingStock;
use validator::ValidationErrorsKind;

use crate::models::prelude::*;
use crate::models::towed_rolling_stock::TowedRollingStockModel;
use crate::models::RollingStockModel;
use crate::CliError;

#[derive(Args, Debug)]
#[command(about, long_about = "Import a rolling stock given a json file")]
Expand All @@ -32,46 +30,24 @@ pub async fn import_rolling_stock(
let rolling_stock: RollingStock =
serde_json::from_reader(BufReader::new(rolling_stock_file))?;
let rolling_stock: Changeset<RollingStockModel> = rolling_stock.into();
Comment on lines 30 to 32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the spirit of "parse don't validate", I'd argue that the validation should occur at the deserialization of the schema and not changeset (unlike what's currently being done on dev). There is an impl From<editoast_schema::RollingStock> for RollingStockChangeset existing in rolling_stock_model.rs. It might also be easier to pull the remote = "Self" trick on the schema as it's struct isn't generated.

Wdyt?

match rolling_stock.validate_imported_rolling_stock() {
Ok(()) => {
println!(
"🍞 Importing rolling stock {}",
rolling_stock
.name
.as_deref()
.unwrap_or("rolling stock without name")
.bold()
);
let rolling_stock = rolling_stock
.locked(false)
.version(0)
.create(&mut db_pool.get().await?)
.await?;
println!(
"✅ Rolling stock {}[{}] saved!",
&rolling_stock.name.bold(),
&rolling_stock.id
);
}
Err(e) => {
let mut error_message = "❌ Rolling stock was not created!".to_string();
if let Some(ValidationErrorsKind::Field(field_errors)) = e.errors().get("__all__") {
for error in field_errors {
if &error.code == "electrical_power_startup_time" {
error_message.push_str(
"\nRolling stock is electrical, but electrical_power_startup_time is missing"
);
}
if &error.code == "raise_pantograph_time" {
error_message.push_str(
"\nRolling stock is electrical, but raise_pantograph_time is missing"
);
}
}
}
return Err(Box::new(CliError::new(2, error_message)));
}
};
println!(
"🍞 Importing rolling stock {}",
rolling_stock
.name
.as_deref()
.unwrap_or("rolling stock without name")
.bold()
);
let rolling_stock = rolling_stock
.locked(false)
.version(0)
.create(&mut db_pool.get().await?)
.await?;
println!(
"✅ Rolling stock {}[{}] saved!",
&rolling_stock.name.bold(),
&rolling_stock.id
);
}
Ok(())
}
Expand Down
116 changes: 82 additions & 34 deletions editoast/src/models/rolling_stock_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod power_restrictions;

use std::collections::HashMap;

use diesel_json::Json;
use editoast_derive::Model;
use editoast_schemas::rolling_stock::EffortCurves;
use editoast_schemas::rolling_stock::EnergySource;
Expand All @@ -13,10 +14,9 @@ use editoast_schemas::rolling_stock::RollingStockMetadata;
use editoast_schemas::rolling_stock::RollingStockSupportedSignalingSystems;
use power_restrictions::PowerRestriction;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use utoipa::ToSchema;
use validator::ValidationError;
use validator::ValidationErrors;

use crate::models::prelude::*;

Expand All @@ -31,7 +31,7 @@ editoast_common::schemas! {
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Model, ToSchema)]
#[model(table = editoast_models::tables::rolling_stock)]
#[model(gen(ops = crud, batch_ops = r, list))]
#[model(changeset(derive(Deserialize), public))]
#[model(changeset(public))]
#[schema(as = RollingStock)]
pub struct RollingStockModel {
pub id: i64,
Expand Down Expand Up @@ -75,50 +75,98 @@ pub struct RollingStockModel {
pub supported_signaling_systems: RollingStockSupportedSignalingSystems,
}

impl RollingStockModelChangeset {
pub fn validate_imported_rolling_stock(&self) -> std::result::Result<(), ValidationErrors> {
match &self.effort_curves {
Some(effort_curves) => validate_rolling_stock(
effort_curves,
self.electrical_power_startup_time.flatten(),
self.raise_pantograph_time.flatten(),
)
.map_err(|e| {
let mut err = ValidationErrors::new();
err.add("effort_curves", e);
err
}),
None => {
let mut err = ValidationErrors::new();
err.add(
"effort_curves",
ValidationError::new("effort_curves is required"),
);
Err(err)
}
impl<'de> Deserialize<'de> for RollingStockModelChangeset {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct Inner {
pub railjson_version: Option<String>,
pub name: Option<String>,
pub effort_curves: Option<Json<EffortCurves>>,
pub metadata: Option<Json<Option<RollingStockMetadata>>>,
pub length: Option<f64>,
pub max_speed: Option<f64>,
pub startup_time: Option<f64>,
pub startup_acceleration: Option<f64>,
pub comfort_acceleration: Option<f64>,
pub const_gamma: Option<f64>,
pub etcs_brake_params: Option<Json<Option<EtcsBrakeParams>>>,
pub inertia_coefficient: Option<f64>,
pub base_power_class: Option<Option<String>>,
pub mass: Option<f64>,
pub rolling_resistance: Option<Json<RollingResistance>>,
pub loading_gauge: Option<i16>,
pub power_restrictions: Option<Json<HashMap<String, String>>>,
pub energy_sources: Option<Json<Vec<EnergySource>>>,
pub locked: Option<bool>,
pub electrical_power_startup_time: Option<Option<f64>>,
pub raise_pantograph_time: Option<Option<f64>>,
pub version: Option<i64>,
pub supported_signaling_systems: Option<Vec<Option<String>>>,
}

let changeset = Inner::deserialize(deserializer)?;

if changeset.effort_curves.is_none() {
return Err(serde::de::Error::custom("effort_curves is required"));
}

validate_rolling_stock::<D>(
changeset.effort_curves.as_ref().unwrap(),
changeset.electrical_power_startup_time.flatten(),
changeset.raise_pantograph_time.flatten(),
)?;

Ok(RollingStockModelChangeset {
railjson_version: changeset.railjson_version,
name: changeset.name,
effort_curves: changeset.effort_curves,
metadata: changeset.metadata,
length: changeset.length,
max_speed: changeset.max_speed,
startup_time: changeset.startup_time,
startup_acceleration: changeset.startup_acceleration,
comfort_acceleration: changeset.comfort_acceleration,
const_gamma: changeset.const_gamma,
etcs_brake_params: changeset.etcs_brake_params,
inertia_coefficient: changeset.inertia_coefficient,
base_power_class: changeset.base_power_class,
mass: changeset.mass,
rolling_resistance: changeset.rolling_resistance,
loading_gauge: changeset.loading_gauge,
power_restrictions: changeset.power_restrictions,
energy_sources: changeset.energy_sources,
locked: changeset.locked,
electrical_power_startup_time: changeset.electrical_power_startup_time,
raise_pantograph_time: changeset.raise_pantograph_time,
version: changeset.version,
supported_signaling_systems: changeset.supported_signaling_systems,
})
}
}

pub fn validate_rolling_stock(
pub fn validate_rolling_stock<'de, D>(
effort_curves: &EffortCurves,
electrical_power_startup_time: Option<f64>,
raise_pantograph_time: Option<f64>,
) -> std::result::Result<(), ValidationError> {
) -> Result<(), D::Error>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that it's part of your TODO list. The errors are going to change, but in the meantime, we might want to stick to the less ideal, but coherent way of doing: using crate::error::Result which uses InternalError.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this function being called by Deserialize::deserialize prevents from using InternalError

where
D: Deserializer<'de>,
{
if !effort_curves.is_electric() {
return Ok(());
}
if electrical_power_startup_time.is_none() {
let mut error = ValidationError::new("electrical_power_startup_time");
error.message =
Some("electrical_power_startup_time is required for electric rolling stocks".into());
return Err(error);
return Err(serde::de::Error::custom(
"electrical_power_startup_time is required for electric rolling stocks",
));
}
if raise_pantograph_time.is_none() {
let mut error = ValidationError::new("raise_pantograph_time");
error.message =
Some("raise_pantograph_time is required for electric rolling stocks".into());
return Err(error);
return Err(serde::de::Error::custom(
"raise_pantograph_time is required for electric rolling stocks",
));
}
Ok(())
}
Expand Down
3 changes: 0 additions & 3 deletions editoast/src/views/rolling_stock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ use strum::Display;
use thiserror::Error;
use utoipa::IntoParams;
use utoipa::ToSchema;
use validator::Validate;

use crate::error::InternalError;
use crate::error::Result;
Expand Down Expand Up @@ -323,7 +322,6 @@ async fn create(
if !authorized {
return Err(AuthorizationError::Forbidden.into());
}
rolling_stock_form.validate()?;
let conn = &mut db_pool.get().await?;
let rolling_stock_name = rolling_stock_form.name.clone();
let rolling_stock_changeset: Changeset<RollingStockModel> = rolling_stock_form.into();
Expand Down Expand Up @@ -361,7 +359,6 @@ async fn update(
if !authorized {
return Err(AuthorizationError::Forbidden.into());
}
rolling_stock_form.validate()?;
let name = rolling_stock_form.name.clone();

let new_rolling_stock = db_pool
Expand Down
44 changes: 30 additions & 14 deletions editoast/src/views/rolling_stock/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ use editoast_schemas::rolling_stock::RollingStockMetadata;
use editoast_schemas::rolling_stock::RollingStockSupportedSignalingSystems;
use editoast_schemas::rolling_stock::ROLLING_STOCK_RAILJSON_VERSION;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;
use utoipa::ToSchema;
use validator::Validate;
use validator::ValidationError;

use crate::models::rolling_stock_model::validate_rolling_stock;
use crate::models::Changeset;
use crate::models::Model;
use crate::models::RollingStockModel;

#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, Validate)]
#[validate(schema(function = "validate_rolling_stock_form"))]
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
#[serde(remote = "Self")]
pub struct RollingStockForm {
pub name: String,
pub effort_curves: EffortCurves,
Expand Down Expand Up @@ -52,6 +52,32 @@ pub struct RollingStockForm {
pub metadata: Option<RollingStockMetadata>,
}

impl<'de> Deserialize<'de> for RollingStockForm {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let rolling_stock_form = RollingStockForm::deserialize(deserializer)?;

validate_rolling_stock::<D>(
&rolling_stock_form.effort_curves,
rolling_stock_form.electrical_power_startup_time,
rolling_stock_form.raise_pantograph_time,
)?;

Ok(rolling_stock_form)
}
}

impl Serialize for RollingStockForm {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
RollingStockForm::serialize(self, serializer)
}
}

impl From<RollingStockForm> for Changeset<RollingStockModel> {
fn from(rolling_stock: RollingStockForm) -> Self {
RollingStockModel::changeset()
Expand Down Expand Up @@ -79,16 +105,6 @@ impl From<RollingStockForm> for Changeset<RollingStockModel> {
}
}

fn validate_rolling_stock_form(
rolling_stock_form: &RollingStockForm,
) -> std::result::Result<(), ValidationError> {
validate_rolling_stock(
&rolling_stock_form.effort_curves,
rolling_stock_form.electrical_power_startup_time,
rolling_stock_form.raise_pantograph_time,
)
}

// Used in some tests where we import a rolling stock as a fixture
#[cfg(test)]
impl From<RollingStockModel> for RollingStockForm {
Expand Down
Loading