use chrono::{DateTime, Utc};
use serde_json::Value;
use crate::error::{AppError, AppResult};
pub fn require_str(payload: &Value, field: &str) -> AppResult<String> {
match payload.get(field) {
Some(Value::String(s)) if !s.trim().is_empty() => Ok(s.trim().to_string()),
Some(Value::String(_)) | None => Err(AppError::Validation(format!(
"payload field `{field}` is required and must be a non-empty string"
))),
Some(_) => Err(AppError::Validation(format!(
"payload field `{field}` must be a string"
))),
}
}
pub fn opt_str(payload: &Value, field: &str) -> Option<String> {
match payload.get(field) {
Some(Value::String(s)) if !s.trim().is_empty() => Some(s.trim().to_string()),
_ => None,
}
}
pub fn opt_datetime(payload: &Value, field: &str) -> AppResult<Option<DateTime<Utc>>> {
match payload.get(field) {
Some(Value::String(s)) if !s.trim().is_empty() => {
let parsed = DateTime::parse_from_rfc3339(s.trim()).map_err(|_| {
AppError::Validation(format!(
"payload field `{field}` must be an RFC 3339 timestamp"
))
})?;
Ok(Some(parsed.with_timezone(&Utc)))
}
Some(Value::Null) | None => Ok(None),
Some(_) => Err(AppError::Validation(format!(
"payload field `{field}` must be a string timestamp"
))),
}
}