use http::StatusCode;
use serde_json::{Map, Value};
use fakecloud_core::service::AwsServiceError;
use crate::generated::{OpMeta, K};
pub const VALIDATION_ERROR: &str = "ValidationException";
pub fn invalid(msg: impl Into<String>) -> AwsServiceError {
AwsServiceError::aws_error(StatusCode::BAD_REQUEST, VALIDATION_ERROR, msg)
}
pub fn validate(meta: &OpMeta, body: &Map<String, Value>) -> Result<(), AwsServiceError> {
for rule in meta.rules {
let present = body.get(rule.wire).map(|v| !v.is_null()).unwrap_or(false);
if rule.req && !present {
return Err(invalid(format!(
"Missing required parameter: {}",
rule.wire
)));
}
if !present {
continue;
}
let val = &body[rule.wire];
if matches!(rule.kind, K::Str) {
let s = match val {
Value::String(s) => s,
_ => {
return Err(invalid(format!("Member {} must be a string.", rule.wire)));
}
};
let len = s.chars().count() as u64;
if let Some(min) = rule.min_len {
if len < min {
return Err(invalid(format!(
"Member {} is shorter than the minimum length {min}.",
rule.wire
)));
}
}
if let Some(max) = rule.max_len {
if len > max {
return Err(invalid(format!(
"Member {} is longer than the maximum length {max}.",
rule.wire
)));
}
}
if !rule.enums.is_empty() && !rule.enums.contains(&s.as_str()) {
return Err(invalid(format!(
"Member {} has an invalid enum value.",
rule.wire
)));
}
}
if matches!(rule.kind, K::Int) && (rule.min_val.is_some() || rule.max_val.is_some()) {
if let Some(n) = val.as_i64() {
if let Some(min) = rule.min_val {
if n < min {
return Err(invalid(format!(
"Member {} is below the minimum {min}.",
rule.wire
)));
}
}
if let Some(max) = rule.max_val {
if n > max {
return Err(invalid(format!(
"Member {} is above the maximum {max}.",
rule.wire
)));
}
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generated::OPS;
fn meta(op: &str) -> &'static OpMeta {
OPS.iter().find(|m| m.op == op).unwrap()
}
#[test]
fn rejects_missing_required_member() {
let m = meta("CreateModel");
let body = Map::new();
assert!(validate(m, &body).is_err());
}
#[test]
fn accepts_valid_body() {
let m = meta("CreateModel");
let mut body = Map::new();
body.insert("ModelName".to_string(), Value::String("m".to_string()));
assert!(validate(m, &body).is_ok());
}
#[test]
fn rejects_invalid_enum() {
let m = meta("CreateApp");
let mut body = Map::new();
body.insert("DomainId".to_string(), Value::String("d".to_string()));
body.insert(
"AppType".to_string(),
Value::String("BogusType".to_string()),
);
body.insert("AppName".to_string(), Value::String("a".to_string()));
assert!(validate(m, &body).is_err());
}
}