systemprompt_api/routes/agent/contexts/webhook/
validation.rs1const MAX_PAYLOAD_SIZE: usize = 1_000_000;
7const MAX_TEXT_FIELD_SIZE: usize = 100_000;
8
9#[derive(Debug, thiserror::Error)]
10pub enum PayloadValidationError {
11 #[error("payload could not be serialized")]
12 Serialize(#[source] serde_json::Error),
13 #[error("Payload too large: {size} bytes (max: {max})")]
14 TooLarge { size: usize, max: usize },
15 #[error("serialized payload could not be re-parsed")]
16 Reparse(#[source] serde_json::Error),
17}
18
19pub fn validate_json_serializable(value: &serde_json::Value) -> Result<(), PayloadValidationError> {
21 let sanitized = sanitize_payload(value, MAX_TEXT_FIELD_SIZE);
22
23 let serialized =
24 serde_json::to_string(&sanitized).map_err(PayloadValidationError::Serialize)?;
25
26 if serialized.len() > MAX_PAYLOAD_SIZE {
27 return Err(PayloadValidationError::TooLarge {
28 size: serialized.len(),
29 max: MAX_PAYLOAD_SIZE,
30 });
31 }
32
33 serde_json::from_str::<serde_json::Value>(&serialized)
34 .map_err(PayloadValidationError::Reparse)?;
35
36 Ok(())
37}
38
39pub fn sanitize_payload(value: &serde_json::Value, max_text_size: usize) -> serde_json::Value {
41 match value {
42 serde_json::Value::String(s) => {
43 if s.len() > max_text_size {
44 serde_json::Value::String(format!(
45 "{}... [truncated from {} bytes]",
46 &s[..max_text_size.min(s.len())],
47 s.len()
48 ))
49 } else {
50 serde_json::Value::String(s.clone())
51 }
52 },
53 serde_json::Value::Array(arr) => serde_json::Value::Array(
54 arr.iter()
55 .map(|v| sanitize_payload(v, max_text_size))
56 .collect(),
57 ),
58 serde_json::Value::Object(obj) => {
59 let sanitized: serde_json::Map<String, serde_json::Value> = obj
60 .iter()
61 .map(|(k, v)| (k.clone(), sanitize_payload(v, max_text_size)))
62 .collect();
63 serde_json::Value::Object(sanitized)
64 },
65 other => other.clone(),
66 }
67}