use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PlanStepStatus {
Pending,
InProgress,
Completed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlanItem {
pub step: String,
pub status: PlanStepStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdatePlanArgs {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub objective: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub explanation: Option<String>,
pub plan: Vec<PlanItem>,
}
impl UpdatePlanArgs {
pub fn validate(&self) -> Result<(), String> {
if let Some(ref objective) = self.objective {
let objective_trimmed = objective.trim();
if objective_trimmed.is_empty() {
return Err("objective must not be empty when provided".to_string());
}
if objective_trimmed.chars().count() > 200 {
return Err(format!(
"objective must be at most 200 characters, got {}",
objective_trimmed.chars().count()
));
}
}
if self.plan.is_empty() {
return Err("plan must contain at least one step".to_string());
}
if self.plan.len() > 50 {
return Err(format!(
"plan must contain at most 50 steps, got {}",
self.plan.len()
));
}
let in_progress_count = self
.plan
.iter()
.filter(|item| item.status == PlanStepStatus::InProgress)
.count();
if in_progress_count > 1 {
return Err(format!(
"at most one step may be in_progress, found {}",
in_progress_count
));
}
for (i, item) in self.plan.iter().enumerate() {
if item.step.trim().is_empty() {
return Err(format!("step[{}] must not be empty", i));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_optional_objective_deserialization() {
let json = r#"{"objective": "Install Docker", "plan": [{"step": "Step 1", "status": "pending"}]}"#;
let args: UpdatePlanArgs = serde_json::from_str(json).unwrap();
assert_eq!(args.objective.as_deref(), Some("Install Docker"));
assert!(args.validate().is_ok());
let json = r#"{"plan": [{"step": "Step 1", "status": "pending"}]}"#;
let args: UpdatePlanArgs = serde_json::from_str(json).unwrap();
assert!(args.objective.is_none());
assert!(args.validate().is_ok());
let json = r#"{"objective": " ", "plan": [{"step": "Step 1", "status": "pending"}]}"#;
let args: UpdatePlanArgs = serde_json::from_str(json).unwrap();
assert!(args.validate().is_err());
}
}