kroki_rs/interface/
errors.rs

1use serde::{Deserialize, Serialize};
2
3/// RFC 7807: Problem Details for HTTP APIs
4/// Provides a standard way to represent errors in a machine-readable format.
5#[derive(Debug, Serialize, Deserialize, Clone)]
6pub struct ProblemDetails {
7    /// A URI reference \[RFC3986\] that identifies the problem type.
8    #[serde(rename = "type")]
9    pub problem_type: String,
10
11    /// A short, human-readable summary of the problem type.
12    pub title: String,
13
14    /// The HTTP status code generated by the origin server for this occurrence of the problem.
15    pub status: u16,
16
17    /// A human-readable explanation specific to this occurrence of the problem.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub detail: Option<String>,
20
21    /// A URI reference that identifies the specific occurrence of the problem.
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub instance: Option<String>,
24}
25
26impl ProblemDetails {
27    pub fn new(problem_type: &str, title: &str, status: u16) -> Self {
28        Self {
29            problem_type: problem_type.to_string(),
30            title: title.to_string(),
31            status,
32            detail: None,
33            instance: None,
34        }
35    }
36
37    pub fn with_detail(mut self, detail: &str) -> Self {
38        self.detail = Some(detail.to_string());
39        self
40    }
41}
42
43impl From<crate::diagrams::error::DiagramError> for ProblemDetails {
44    fn from(err: crate::diagrams::error::DiagramError) -> Self {
45        use crate::diagrams::error::DiagramError::*;
46
47        let (status, problem_type, title) = match &err {
48            ValidationFailed(_) => (400, "validation-failed", "Input Validation Failed"),
49            DecodeFailed(_) => (400, "decode-failed", "Input Decoding Failed"),
50            UnsupportedFormat { .. } => (400, "unsupported-format", "Unsupported Format"),
51            ToolNotFound(_) => (503, "tool-not-found", "Rendering Tool Missing"),
52            ExecutionTimeout { .. } => (504, "execution-timeout", "Rendering Timed Out"),
53            ProcessFailed(_) | Io(_) | Internal(_) => {
54                (500, "internal-error", "Internal Server Error")
55            }
56        };
57
58        Self::new(
59            &format!("https://kroki.io/errors/{}", problem_type),
60            title,
61            status,
62        )
63        .with_detail(&err.to_string())
64    }
65}