1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ProblemDetails {
10 #[serde(skip_serializing_if = "Option::is_none", rename = "type")]
11 pub typ: Option<String>,
12 pub title: String,
13 pub status: u16,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub detail: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub instance: Option<String>,
18 #[serde(skip_serializing_if = "Option::is_none", flatten)]
19 pub extensions: Option<HashMap<String, serde_json::Value>>,
20}
21
22impl ProblemDetails {
23 pub fn new(status: u16, title: impl Into<String>) -> Self {
24 Self {
25 typ: None,
26 title: title.into(),
27 status,
28 detail: None,
29 instance: None,
30 extensions: None,
31 }
32 }
33
34 pub fn not_found(resource: impl Into<String>, id: impl std::fmt::Display) -> Self {
35 let r = resource.into();
36 let is = format!("{}", id);
37 Self {
38 typ: Some("https://httpstatuses.com/404".into()),
39 title: "Resource Not Found".into(),
40 status: 404,
41 detail: Some(format!("{} '{}' not found", r, is)),
42 instance: Some(format!("/{}s/{}", r.to_lowercase(), is)),
43 extensions: None,
44 }
45 }
46
47 pub fn validation(errors: Vec<FieldError>) -> Self {
56 let mut grouped: std::collections::BTreeMap<String, Vec<String>> =
57 std::collections::BTreeMap::new();
58 for error in errors {
59 grouped.entry(error.field).or_default().push(error.message);
60 }
61
62 let mut ext = HashMap::new();
63 ext.insert(
64 "errors".into(),
65 serde_json::to_value(grouped).unwrap_or_default(),
66 );
67 Self {
68 typ: Some("https://httpstatuses.com/400".into()),
69 title: "Validation Failed".into(),
70 status: 400,
71 detail: Some("One or more validation errors occurred".into()),
72 instance: None,
73 extensions: Some(ext),
74 }
75 }
76
77 pub fn with_detail(mut self, d: impl Into<String>) -> Self {
78 self.detail = Some(d.into());
79 self
80 }
81
82 pub fn with_instance(mut self, i: impl Into<String>) -> Self {
83 self.instance = Some(i.into());
84 self
85 }
86
87 pub fn to_error(self) -> crate::error::Error {
92 crate::error::Error::Problem(Box::new(self))
93 }
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct FieldError {
98 pub field: String,
99 pub message: String,
100}
101
102impl FieldError {
103 pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
104 Self {
105 field: field.into(),
106 message: message.into(),
107 }
108 }
109}