use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RpcErrorCode {
InvalidArgument,
NotFound,
AlreadyExists,
Unauthenticated,
Internal,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RpcEnvelope {
pub code: RpcErrorCode,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub correlation_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
mod rpc_envelope {
use super::*;
mod round_trip_json {
use super::*;
#[test]
fn preserves_code_message_correlation_and_details() {
let env = RpcEnvelope {
code: RpcErrorCode::InvalidArgument,
message: "bad".to_owned(),
correlation_id: Some("cid-1".to_owned()),
details: Some(json!({"field": "age"})),
};
let bytes = serde_json::to_vec(&env).expect("serialize");
let back: RpcEnvelope = serde_json::from_slice(&bytes).expect("deserialize");
assert_eq!(back, env);
}
#[test]
fn omits_null_optional_fields_on_wire() {
let env = RpcEnvelope {
code: RpcErrorCode::Internal,
message: "x".to_owned(),
correlation_id: None,
details: None,
};
let v: serde_json::Value = serde_json::to_value(&env).expect("to_value");
assert!(v.get("correlation_id").is_none());
assert!(v.get("details").is_none());
}
}
mod rpc_error_code {
use super::*;
#[test]
fn serde_snake_case_round_trip() {
let codes = [
RpcErrorCode::InvalidArgument,
RpcErrorCode::NotFound,
RpcErrorCode::AlreadyExists,
RpcErrorCode::Unauthenticated,
RpcErrorCode::Internal,
];
for code in codes {
let s = serde_json::to_string(&code).expect("ser");
let back: RpcErrorCode = serde_json::from_str(&s).expect("de");
assert_eq!(back, code);
}
}
}
}
}