silicon_browser_shared/
error.rs1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::RequestId;
6
7#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
9pub struct Envelope<T> {
10 pub data: T,
11 #[serde(default, skip_serializing_if = "Option::is_none")]
12 pub request_id: Option<RequestId>,
13}
14
15impl<T> Envelope<T> {
16 pub fn new(data: T) -> Self {
17 Self { data, request_id: None }
18 }
19
20 pub fn with_request_id(data: T, request_id: impl Into<RequestId>) -> Self {
21 Self { data, request_id: Some(request_id.into()) }
22 }
23
24 pub fn map<U>(self, map: impl FnOnce(T) -> U) -> Envelope<U> {
25 Envelope { data: map(self.data), request_id: self.request_id }
26 }
27}
28
29#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
31pub struct FieldError {
32 pub field: String,
33 pub message: String,
34}
35
36#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
38pub struct ApiError {
39 pub code: String,
40 pub message: String,
41 #[serde(default, skip_serializing_if = "Vec::is_empty")]
42 pub fields: Vec<FieldError>,
43 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
44 pub details: BTreeMap<String, String>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub request_id: Option<RequestId>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub retry_after_ms: Option<u64>,
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
53pub struct ApiErrorEnvelope {
54 pub error: ApiError,
55}
56
57impl ApiErrorEnvelope {
58 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
59 Self {
60 error: ApiError {
61 code: code.into(),
62 message: message.into(),
63 fields: Vec::new(),
64 details: BTreeMap::new(),
65 request_id: None,
66 retry_after_ms: None,
67 },
68 }
69 }
70}
71
72#[cfg(test)]
73mod api_envelope_tests {
74 use super::*;
75
76 #[test]
78 fn envelope_maps_without_losing_request_id() {
79 let response = Envelope::with_request_id(4, "request-1").map(|value| value * 2);
80 assert_eq!(response.data, 8);
81 assert_eq!(response.request_id.as_deref(), Some("request-1"));
82 }
83
84 #[test]
86 fn error_envelope_serializes_under_error_key() {
87 let json = serde_json::to_value(ApiErrorEnvelope::new("not_found", "missing")).unwrap();
88 assert_eq!(json["error"]["code"], "not_found");
89 assert!(json.get("data").is_none());
90 }
91}