fix(control): validate unit timing configuration
This commit is contained in:
parent
86e651d9ca
commit
dbfa673468
|
|
@ -18,6 +18,23 @@ use crate::{
|
||||||
AppState,
|
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)]
|
#[derive(Debug, Deserialize, Validate)]
|
||||||
pub struct GetUnitListQuery {
|
pub struct GetUnitListQuery {
|
||||||
#[validate(length(min = 1, max = 100))]
|
#[validate(length(min = 1, max = 100))]
|
||||||
|
|
@ -293,13 +310,13 @@ pub struct CreateUnitReq {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
#[validate(range(min = 0))]
|
#[validate(range(min = 1, message = "must be greater than 0"))]
|
||||||
pub run_time_sec: Option<i32>,
|
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>,
|
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>,
|
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 bl_time_sec: Option<i32>,
|
||||||
pub require_manual_ack_after_fault: Option<bool>,
|
pub require_manual_ack_after_fault: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
@ -310,6 +327,33 @@ pub async fn create_unit(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
payload.validate()?;
|
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)
|
if crate::service::get_unit_by_code(&state.pool, &payload.code)
|
||||||
.await?
|
.await?
|
||||||
.is_some()
|
.is_some()
|
||||||
|
|
@ -327,10 +371,10 @@ pub async fn create_unit(
|
||||||
name: &payload.name,
|
name: &payload.name,
|
||||||
description: payload.description.as_deref(),
|
description: payload.description.as_deref(),
|
||||||
enabled: payload.enabled.unwrap_or(true),
|
enabled: payload.enabled.unwrap_or(true),
|
||||||
run_time_sec: payload.run_time_sec.unwrap_or(0),
|
run_time_sec,
|
||||||
stop_time_sec: payload.stop_time_sec.unwrap_or(0),
|
stop_time_sec,
|
||||||
acc_time_sec: payload.acc_time_sec.unwrap_or(0),
|
acc_time_sec,
|
||||||
bl_time_sec: payload.bl_time_sec.unwrap_or(0),
|
bl_time_sec,
|
||||||
require_manual_ack_after_fault: payload
|
require_manual_ack_after_fault: payload
|
||||||
.require_manual_ack_after_fault
|
.require_manual_ack_after_fault
|
||||||
.unwrap_or(true),
|
.unwrap_or(true),
|
||||||
|
|
@ -355,13 +399,13 @@ pub struct UpdateUnitReq {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
#[validate(range(min = 0))]
|
#[validate(range(min = 1, message = "must be greater than 0"))]
|
||||||
pub run_time_sec: Option<i32>,
|
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>,
|
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>,
|
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 bl_time_sec: Option<i32>,
|
||||||
pub require_manual_ack_after_fault: Option<bool>,
|
pub require_manual_ack_after_fault: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
@ -373,12 +417,14 @@ pub async fn update_unit(
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
payload.validate()?;
|
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?
|
.await?
|
||||||
.is_none()
|
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||||
{
|
|
||||||
return Err(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() {
|
if let Some(code) = payload.code.as_deref() {
|
||||||
let duplicate = crate::service::get_unit_by_code(&state.pool, code).await?;
|
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))
|
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.unitId.value = "";
|
||||||
dom.unitEnabled.checked = true;
|
dom.unitEnabled.checked = true;
|
||||||
dom.unitManualAck.checked = true;
|
dom.unitManualAck.checked = true;
|
||||||
dom.unitRunTimeSec.value = "0";
|
dom.unitRunTimeSec.value = "10";
|
||||||
dom.unitStopTimeSec.value = "0";
|
dom.unitStopTimeSec.value = "10";
|
||||||
dom.unitAccTimeSec.value = "0";
|
dom.unitAccTimeSec.value = "20";
|
||||||
dom.unitBlTimeSec.value = "0";
|
dom.unitBlTimeSec.value = "10";
|
||||||
}
|
}
|
||||||
|
|
||||||
function openUnitModal() {
|
function openUnitModal() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue