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")]
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 {
48 let mut ext = HashMap::new();
49 ext.insert(
50 "errors".into(),
51 serde_json::to_value(errors).unwrap_or_default(),
52 );
53 Self {
54 typ: Some("https://httpstatuses.com/400".into()),
55 title: "Validation Failed".into(),
56 status: 400,
57 detail: Some("One or more validation errors occurred".into()),
58 instance: None,
59 extensions: Some(ext),
60 }
61 }
62
63 pub fn with_detail(mut self, d: impl Into<String>) -> Self {
64 self.detail = Some(d.into());
65 self
66 }
67
68 pub fn with_instance(mut self, i: impl Into<String>) -> Self {
69 self.instance = Some(i.into());
70 self
71 }
72
73 pub fn to_error(self) -> crate::error::Error {
74 let title = self.title.clone();
75 let fallback = serde_json::to_string(&self).unwrap_or(title);
76 crate::error::Error::Http(fallback)
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct FieldError {
82 pub field: String,
83 pub message: String,
84}
85
86impl FieldError {
87 pub fn new(field: impl Into<String>, message: impl Into<String>) -> Self {
88 Self {
89 field: field.into(),
90 message: message.into(),
91 }
92 }
93}