problem_spec/
problem_spec.rs1use crate::status::Status;
2use bon::Builder;
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Serialize, Deserialize)]
7pub enum ProblemType {
8 ValidationError,
9 NotFound,
10 InternalServerError,
11 Custom(String),
12}
13
14impl ProblemType {
15 fn as_str(&self) -> String {
16 match self {
17 ProblemType::ValidationError => {
18 "https://example.com/problems/validation-error".to_string()
19 }
20 ProblemType::NotFound => "https://example.com/problems/not-found".to_string(),
21 ProblemType::InternalServerError => {
22 "https://example.com/problems/internal-server-error".to_string()
23 }
24 ProblemType::Custom(s) => s.clone(),
25 }
26 }
27}
28
29#[derive(Debug, Serialize, Deserialize)]
30pub struct ExtraKv {
31 inner: HashMap<String, String>,
32}
33
34impl ExtraKv {
35 fn new() -> Self {
36 ExtraKv {
37 inner: HashMap::new(),
38 }
39 }
40
41 fn is_empty(&self) -> bool {
42 self.inner.is_empty()
43 }
44
45 fn insert(mut self, k: &str, v: &str) -> Self {
46 self.inner.insert(k.to_string(), v.to_string());
47 self
48 }
49}
50
51#[derive(Debug, Serialize, Deserialize, Builder)]
52pub struct Problem {
53 #[serde(rename = "type")]
54 #[serde(default = "default_problem_type")]
55 pub problem_type: String,
56
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub title: Option<String>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub status: Option<Status>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub detail: Option<String>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub instance: Option<String>,
68
69 #[serde(flatten)]
70 #[serde(skip_serializing_if = "ExtraKv::is_empty")]
71 pub extra: ExtraKv,
72}
73
74fn default_problem_type() -> String {
75 "about:blank".to_string()
76}
77
78impl Problem {
79 pub fn new() -> Self {
80 Problem {
81 problem_type: default_problem_type(),
82 title: None,
83 status: None,
84 detail: None,
85 instance: None,
86 extra: ExtraKv::new(),
87 }
88 }
89
90 pub fn problem_type(mut self, problem_type: ProblemType) -> Self {
98 self.problem_type = problem_type.as_str();
99 self
100 }
101}
102
103#[cfg(test)]
104mod test {
105 use crate::problem_spec::{ExtraKv, Problem, ProblemType};
106 use std::collections::HashMap;
107
108 #[test]
109 fn test_ok() {
110 let extra = ExtraKv::new();
117
118 let problem_spec = Problem::builder()
119 .problem_type(ProblemType::ValidationError.as_str())
120 .title("happen".to_string())
121 .detail("fund".to_string())
122 .extra(extra)
123 .build();
124
125 println!("{:?}", serde_json::to_string(&problem_spec).unwrap());
126 }
127}