126 lines
4.0 KiB
Rust
126 lines
4.0 KiB
Rust
//! # brokers/payload.rs — AMQP Envelope Contracts
|
|
//!
|
|
//! Strict 1.0 request/response contracts for broker message bodies.
|
|
|
|
use std::collections::HashMap;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
|
|
pub const ENVELOPE_VERSION: &str = "1.0";
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct BrokerRequestEnvelope {
|
|
pub version: String,
|
|
pub op: String,
|
|
pub template: String,
|
|
|
|
#[serde(default)]
|
|
pub correlation_id: String,
|
|
|
|
#[serde(default)]
|
|
pub payload: HashMap<String, Value>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct BrokerResponseEnvelope {
|
|
pub version: String,
|
|
pub op: String,
|
|
pub correlation_id: String,
|
|
pub status: String,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub error_code: Option<String>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub message: Option<String>,
|
|
|
|
pub payload: Value,
|
|
}
|
|
|
|
pub fn parse_request(bytes: &[u8]) -> Result<BrokerRequestEnvelope, String> {
|
|
let env: BrokerRequestEnvelope =
|
|
serde_json::from_slice(bytes).map_err(|e| format!("invalid envelope JSON: {}", e))?;
|
|
|
|
if env.version != ENVELOPE_VERSION {
|
|
return Err(format!(
|
|
"unsupported envelope version '{}', expected '{}'",
|
|
env.version, ENVELOPE_VERSION
|
|
));
|
|
}
|
|
if env.op.trim().is_empty() {
|
|
return Err("missing required field 'op'".to_string());
|
|
}
|
|
if env.template.trim().is_empty() {
|
|
return Err("missing required field 'template'".to_string());
|
|
}
|
|
|
|
Ok(env)
|
|
}
|
|
|
|
pub fn success_response(op: &str, correlation_id: &str, payload: Value) -> Vec<u8> {
|
|
let response = BrokerResponseEnvelope {
|
|
version: ENVELOPE_VERSION.to_string(),
|
|
op: op.to_string(),
|
|
correlation_id: correlation_id.to_string(),
|
|
status: "ok".to_string(),
|
|
error_code: None,
|
|
message: None,
|
|
payload,
|
|
};
|
|
serde_json::to_vec(&response).unwrap_or_else(|_| b"{}".to_vec())
|
|
}
|
|
|
|
pub fn error_response(op: &str, correlation_id: &str, code: &str, message: &str) -> Vec<u8> {
|
|
let response = BrokerResponseEnvelope {
|
|
version: ENVELOPE_VERSION.to_string(),
|
|
op: op.to_string(),
|
|
correlation_id: correlation_id.to_string(),
|
|
status: "error".to_string(),
|
|
error_code: Some(code.to_string()),
|
|
message: Some(message.to_string()),
|
|
payload: Value::Object(serde_json::Map::new()),
|
|
};
|
|
serde_json::to_vec(&response).unwrap_or_else(|_| b"{}".to_vec())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn parses_valid_request_envelope() {
|
|
let json = r#"{"version":"1.0","op":"fetch","template":"Logger","correlation_id":"c1","payload":{"limit":10}}"#;
|
|
let env = parse_request(json.as_bytes()).expect("parse should succeed");
|
|
assert_eq!(env.version, "1.0");
|
|
assert_eq!(env.op, "fetch");
|
|
assert_eq!(env.template, "Logger");
|
|
assert_eq!(env.correlation_id, "c1");
|
|
assert_eq!(env.payload["limit"], 10);
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_legacy_unversioned_shape() {
|
|
let json = r#"{"template":"Logger","data":{"limit":10}}"#;
|
|
let err = parse_request(json.as_bytes()).expect_err("parse should fail");
|
|
assert!(err.contains("invalid envelope JSON") || err.contains("missing required"));
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_wrong_version() {
|
|
let json = r#"{"version":"0.9","op":"fetch","template":"Logger","payload":{}}"#;
|
|
let err = parse_request(json.as_bytes()).expect_err("parse should fail");
|
|
assert!(err.contains("unsupported envelope version"));
|
|
}
|
|
|
|
#[test]
|
|
fn builds_error_response_envelope() {
|
|
let bytes = error_response("write", "c123", "INVALID_PAYLOAD", "bad");
|
|
let env: BrokerResponseEnvelope = serde_json::from_slice(&bytes).expect("response should parse");
|
|
assert_eq!(env.version, ENVELOPE_VERSION);
|
|
assert_eq!(env.op, "write");
|
|
assert_eq!(env.correlation_id, "c123");
|
|
assert_eq!(env.status, "error");
|
|
assert_eq!(env.error_code.as_deref(), Some("INVALID_PAYLOAD"));
|
|
}
|
|
}
|