modkit_canonical_errors/
problem.rs1use serde::{Deserialize, Serialize};
2
3use crate::error::CanonicalError;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Problem {
11 #[serde(rename = "type")]
12 pub problem_type: String,
13 pub title: String,
14 pub status: u16,
15 pub detail: String,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub instance: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none")]
19 pub trace_id: Option<String>,
20 pub context: serde_json::Value,
21}
22
23impl Problem {
24 pub fn from_error(err: &CanonicalError) -> Result<Self, serde_json::Error> {
33 let problem_type = format!("gts://{}", err.gts_type());
34 let title = err.title().to_owned();
35 let status = err.status_code();
36 let detail = err.detail().to_owned();
37
38 let mut context = serialize_context(err)?;
39
40 if let Some(rt) = err.resource_type() {
41 context["resource_type"] = serde_json::Value::String(rt.to_owned());
42 }
43
44 if let Some(rn) = err.resource_name() {
45 context["resource_name"] = serde_json::Value::String(rn.to_owned());
46 }
47
48 Ok(Problem {
49 problem_type,
50 title,
51 status,
52 detail,
53 instance: None,
54 trace_id: None,
55 context,
56 })
57 }
58
59 pub fn from_error_debug(err: &CanonicalError) -> Result<Self, serde_json::Error> {
74 let mut problem = Self::from_error(err)?;
75
76 if let Some(diag) = err.diagnostic() {
77 problem.context["description"] = serde_json::Value::String(diag.to_owned());
78 }
79
80 Ok(problem)
81 }
82
83 #[must_use]
85 pub fn with_trace_id(mut self, trace_id: impl Into<String>) -> Self {
86 self.trace_id = Some(trace_id.into());
87 self
88 }
89
90 #[must_use]
92 pub fn with_instance(mut self, instance: impl Into<String>) -> Self {
93 self.instance = Some(instance.into());
94 self
95 }
96}
97
98fn serialize_context(err: &CanonicalError) -> Result<serde_json::Value, serde_json::Error> {
99 match err {
100 CanonicalError::Cancelled { ctx, .. } => serde_json::to_value(ctx),
101 CanonicalError::Unknown { ctx, .. } => serde_json::to_value(ctx),
102 CanonicalError::InvalidArgument { ctx, .. } => serde_json::to_value(ctx),
103 CanonicalError::DeadlineExceeded { ctx, .. } => serde_json::to_value(ctx),
104 CanonicalError::NotFound { ctx, .. } => serde_json::to_value(ctx),
105 CanonicalError::AlreadyExists { ctx, .. } => serde_json::to_value(ctx),
106 CanonicalError::PermissionDenied { ctx, .. } => serde_json::to_value(ctx),
107 CanonicalError::ResourceExhausted { ctx, .. } => serde_json::to_value(ctx),
108 CanonicalError::FailedPrecondition { ctx, .. } => serde_json::to_value(ctx),
109 CanonicalError::Aborted { ctx, .. } => serde_json::to_value(ctx),
110 CanonicalError::OutOfRange { ctx, .. } => serde_json::to_value(ctx),
111 CanonicalError::Unimplemented { ctx, .. } => serde_json::to_value(ctx),
112 CanonicalError::Internal { ctx, .. } => serde_json::to_value(ctx),
113 CanonicalError::ServiceUnavailable { ctx, .. } => serde_json::to_value(ctx),
114 CanonicalError::DataLoss { ctx, .. } => serde_json::to_value(ctx),
115 CanonicalError::Unauthenticated { ctx, .. } => serde_json::to_value(ctx),
116 }
117}
118
119impl From<CanonicalError> for Problem {
120 fn from(err: CanonicalError) -> Self {
121 match Problem::from_error(&err) {
122 Ok(p) => p,
123 Err(ser_err) => Problem {
124 problem_type: format!("gts://{}", err.gts_type()),
125 title: err.title().to_owned(),
126 status: err.status_code(),
127 detail: err.detail().to_owned(),
128 instance: None,
129 trace_id: None,
130 context: serde_json::Value::String(ser_err.to_string()),
131 },
132 }
133 }
134}