use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
const EVENT_FIELD: &str = "event";
const ATTACHMENT_FIELD: &str = "attachment_b64";
pub fn encode_submit_body(event_json: &[u8], attachment: &[u8]) -> Result<Vec<u8>, String> {
if attachment.is_empty() {
return Ok(event_json.to_vec());
}
let event: serde_json::Value =
serde_json::from_slice(event_json).map_err(|e| format!("event is not JSON: {e}"))?;
let envelope = serde_json::json!({
EVENT_FIELD: event,
ATTACHMENT_FIELD: URL_SAFE_NO_PAD.encode(attachment),
});
serde_json::to_vec(&envelope).map_err(|e| format!("envelope serialization: {e}"))
}
pub fn split_submit_body(body: serde_json::Value) -> Result<(serde_json::Value, Vec<u8>), String> {
let Some(obj) = body.as_object() else {
return Err("request body must be a JSON object".to_string());
};
if !obj.contains_key(EVENT_FIELD) {
return Ok((body, Vec::new()));
}
let event = obj
.get(EVENT_FIELD)
.cloned()
.ok_or_else(|| "envelope missing 'event'".to_string())?;
if !event.is_object() {
return Err("envelope 'event' must be a JSON object".to_string());
}
let attachment = match obj.get(ATTACHMENT_FIELD) {
None => Vec::new(),
Some(serde_json::Value::String(b64)) => URL_SAFE_NO_PAD
.decode(b64)
.map_err(|e| format!("attachment_b64 is not base64url-no-pad: {e}"))?,
Some(_) => return Err("'attachment_b64' must be a string".to_string()),
};
Ok((event, attachment))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bare_event_passes_through() {
let body = serde_json::json!({"t": "icp", "d": "E1"});
let (event, attachment) = split_submit_body(body.clone()).unwrap();
assert_eq!(event, body);
assert!(attachment.is_empty());
}
#[test]
fn envelope_roundtrips() {
let event = serde_json::json!({"t": "rot", "d": "E2"});
let attachment = b"-AABAAfake".to_vec();
let body = encode_submit_body(&serde_json::to_vec(&event).unwrap(), &attachment).unwrap();
let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
let (out_event, out_attachment) = split_submit_body(parsed).unwrap();
assert_eq!(out_event, event);
assert_eq!(out_attachment, attachment);
}
#[test]
fn empty_attachment_encodes_bare() {
let event = serde_json::json!({"t": "icp"});
let body = encode_submit_body(&serde_json::to_vec(&event).unwrap(), &[]).unwrap();
let parsed: serde_json::Value = serde_json::from_slice(&body).unwrap();
assert_eq!(parsed, event);
}
#[test]
fn bad_base64_is_rejected() {
let body = serde_json::json!({"event": {"t": "icp"}, "attachment_b64": "!!!"});
assert!(split_submit_body(body).is_err());
}
}