fix(control): validate unit timing configuration
This commit is contained in:
parent
86e651d9ca
commit
dbfa673468
|
|
@ -18,6 +18,23 @@ use crate::{
|
|||
AppState,
|
||||
};
|
||||
|
||||
fn validate_unit_timing_order(
|
||||
run_time_sec: i32,
|
||||
acc_time_sec: i32,
|
||||
) -> Result<(), ApiErr> {
|
||||
if acc_time_sec <= run_time_sec {
|
||||
return Err(ApiErr::BadRequest(
|
||||
"acc_time_sec must be greater than run_time_sec".to_string(),
|
||||
Some(json!({
|
||||
"run_time_sec": ["must be less than acc_time_sec"],
|
||||
"acc_time_sec": ["must be greater than run_time_sec"]
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
pub struct GetUnitListQuery {
|
||||
#[validate(length(min = 1, max = 100))]
|
||||
|
|
@ -293,13 +310,13 @@ pub struct CreateUnitReq {
|
|||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub enabled: Option<bool>,
|
||||
#[validate(range(min = 0))]
|
||||
#[validate(range(min = 1, message = "must be greater than 0"))]
|
||||
pub run_time_sec: Option<i32>,
|
||||
#[validate(range(min = 0))]
|
||||
#[validate(range(min = 1, message = "must be greater than 0"))]
|
||||
pub stop_time_sec: Option<i32>,
|
||||
#[validate(range(min = 0))]
|
||||
#[validate(range(min = 1, message = "must be greater than 0"))]
|
||||
pub acc_time_sec: Option<i32>,
|
||||
#[validate(range(min = 0))]
|
||||
#[validate(range(min = 1, message = "must be greater than 0"))]
|
||||
pub bl_time_sec: Option<i32>,
|
||||
pub require_manual_ack_after_fault: Option<bool>,
|
||||
}
|
||||
|
|
@ -310,6 +327,33 @@ pub async fn create_unit(
|
|||
) -> Result<impl IntoResponse, ApiErr> {
|
||||
payload.validate()?;
|
||||
|
||||
let run_time_sec = payload.run_time_sec.ok_or_else(|| {
|
||||
ApiErr::BadRequest(
|
||||
"run_time_sec is required".to_string(),
|
||||
Some(json!({ "run_time_sec": ["is required"] })),
|
||||
)
|
||||
})?;
|
||||
let stop_time_sec = payload.stop_time_sec.ok_or_else(|| {
|
||||
ApiErr::BadRequest(
|
||||
"stop_time_sec is required".to_string(),
|
||||
Some(json!({ "stop_time_sec": ["is required"] })),
|
||||
)
|
||||
})?;
|
||||
let acc_time_sec = payload.acc_time_sec.ok_or_else(|| {
|
||||
ApiErr::BadRequest(
|
||||
"acc_time_sec is required".to_string(),
|
||||
Some(json!({ "acc_time_sec": ["is required"] })),
|
||||
)
|
||||
})?;
|
||||
let bl_time_sec = payload.bl_time_sec.ok_or_else(|| {
|
||||
ApiErr::BadRequest(
|
||||
"bl_time_sec is required".to_string(),
|
||||
Some(json!({ "bl_time_sec": ["is required"] })),
|
||||
)
|
||||
})?;
|
||||
|
||||
validate_unit_timing_order(run_time_sec, acc_time_sec)?;
|
||||
|
||||
if crate::service::get_unit_by_code(&state.pool, &payload.code)
|
||||
.await?
|
||||
.is_some()
|
||||
|
|
@ -327,10 +371,10 @@ pub async fn create_unit(
|
|||
name: &payload.name,
|
||||
description: payload.description.as_deref(),
|
||||
enabled: payload.enabled.unwrap_or(true),
|
||||
run_time_sec: payload.run_time_sec.unwrap_or(0),
|
||||
stop_time_sec: payload.stop_time_sec.unwrap_or(0),
|
||||
acc_time_sec: payload.acc_time_sec.unwrap_or(0),
|
||||
bl_time_sec: payload.bl_time_sec.unwrap_or(0),
|
||||
run_time_sec,
|
||||
stop_time_sec,
|
||||
acc_time_sec,
|
||||
bl_time_sec,
|
||||
require_manual_ack_after_fault: payload
|
||||
.require_manual_ack_after_fault
|
||||
.unwrap_or(true),
|
||||
|
|
@ -355,13 +399,13 @@ pub struct UpdateUnitReq {
|
|||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub enabled: Option<bool>,
|
||||
#[validate(range(min = 0))]
|
||||
#[validate(range(min = 1, message = "must be greater than 0"))]
|
||||
pub run_time_sec: Option<i32>,
|
||||
#[validate(range(min = 0))]
|
||||
#[validate(range(min = 1, message = "must be greater than 0"))]
|
||||
pub stop_time_sec: Option<i32>,
|
||||
#[validate(range(min = 0))]
|
||||
#[validate(range(min = 1, message = "must be greater than 0"))]
|
||||
pub acc_time_sec: Option<i32>,
|
||||
#[validate(range(min = 0))]
|
||||
#[validate(range(min = 1, message = "must be greater than 0"))]
|
||||
pub bl_time_sec: Option<i32>,
|
||||
pub require_manual_ack_after_fault: Option<bool>,
|
||||
}
|
||||
|
|
@ -373,12 +417,14 @@ pub async fn update_unit(
|
|||
) -> Result<impl IntoResponse, ApiErr> {
|
||||
payload.validate()?;
|
||||
|
||||
if crate::service::get_unit_by_id(&state.pool, unit_id)
|
||||
let existing_unit = crate::service::get_unit_by_id(&state.pool, unit_id)
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
return Err(ApiErr::NotFound("Unit not found".to_string(), None));
|
||||
}
|
||||
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||
|
||||
validate_unit_timing_order(
|
||||
payload.run_time_sec.unwrap_or(existing_unit.run_time_sec),
|
||||
payload.acc_time_sec.unwrap_or(existing_unit.acc_time_sec),
|
||||
)?;
|
||||
|
||||
if let Some(code) = payload.code.as_deref() {
|
||||
let duplicate = crate::service::get_unit_by_code(&state.pool, code).await?;
|
||||
|
|
@ -627,3 +673,52 @@ pub async fn get_unit_runtime(
|
|||
Ok(Json(runtime))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{validate_unit_timing_order, CreateUnitReq, UpdateUnitReq};
|
||||
use validator::Validate;
|
||||
|
||||
#[test]
|
||||
fn create_unit_req_rejects_zero_second_fields() {
|
||||
let payload = CreateUnitReq {
|
||||
code: "U-01".to_string(),
|
||||
name: "Unit 01".to_string(),
|
||||
description: None,
|
||||
enabled: Some(true),
|
||||
run_time_sec: Some(0),
|
||||
stop_time_sec: Some(10),
|
||||
acc_time_sec: Some(20),
|
||||
bl_time_sec: Some(5),
|
||||
require_manual_ack_after_fault: Some(true),
|
||||
};
|
||||
|
||||
assert!(payload.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_unit_req_rejects_acc_time_not_greater_than_run_time() {
|
||||
assert!(validate_unit_timing_order(10, 10).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_unit_req_rejects_zero_second_fields() {
|
||||
let payload = UpdateUnitReq {
|
||||
code: None,
|
||||
name: None,
|
||||
description: None,
|
||||
enabled: None,
|
||||
run_time_sec: None,
|
||||
stop_time_sec: Some(0),
|
||||
acc_time_sec: Some(20),
|
||||
bl_time_sec: Some(5),
|
||||
require_manual_ack_after_fault: None,
|
||||
};
|
||||
|
||||
assert!(payload.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_unit_req_rejects_acc_time_not_greater_than_run_time_when_both_present() {
|
||||
assert!(validate_unit_timing_order(20, 15).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ export function resetUnitForm() {
|
|||
dom.unitId.value = "";
|
||||
dom.unitEnabled.checked = true;
|
||||
dom.unitManualAck.checked = true;
|
||||
dom.unitRunTimeSec.value = "0";
|
||||
dom.unitStopTimeSec.value = "0";
|
||||
dom.unitAccTimeSec.value = "0";
|
||||
dom.unitBlTimeSec.value = "0";
|
||||
dom.unitRunTimeSec.value = "10";
|
||||
dom.unitStopTimeSec.value = "10";
|
||||
dom.unitAccTimeSec.value = "20";
|
||||
dom.unitBlTimeSec.value = "10";
|
||||
}
|
||||
|
||||
function openUnitModal() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue