use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::RequestId;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Envelope<T> {
pub data: T,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request_id: Option<RequestId>,
}
impl<T> Envelope<T> {
pub fn new(data: T) -> Self {
Self { data, request_id: None }
}
pub fn with_request_id(data: T, request_id: impl Into<RequestId>) -> Self {
Self { data, request_id: Some(request_id.into()) }
}
pub fn map<U>(self, map: impl FnOnce(T) -> U) -> Envelope<U> {
Envelope { data: map(self.data), request_id: self.request_id }
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FieldError {
pub field: String,
pub message: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiError {
pub code: String,
pub message: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fields: Vec<FieldError>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub details: BTreeMap<String, String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request_id: Option<RequestId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub retry_after_ms: Option<u64>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiErrorEnvelope {
pub error: ApiError,
}
impl ApiErrorEnvelope {
pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
Self {
error: ApiError {
code: code.into(),
message: message.into(),
fields: Vec::new(),
details: BTreeMap::new(),
request_id: None,
retry_after_ms: None,
},
}
}
}
#[cfg(test)]
mod api_envelope_tests {
use super::*;
#[test]
fn envelope_maps_without_losing_request_id() {
let response = Envelope::with_request_id(4, "request-1").map(|value| value * 2);
assert_eq!(response.data, 8);
assert_eq!(response.request_id.as_deref(), Some("request-1"));
}
#[test]
fn error_envelope_serializes_under_error_key() {
let json = serde_json::to_value(ApiErrorEnvelope::new("not_found", "missing")).unwrap();
assert_eq!(json["error"]["code"], "not_found");
assert!(json.get("data").is_none());
}
}