systemprompt_models/api/errors/
mod.rs1mod extension;
18mod identifier;
19mod repository;
20#[cfg(feature = "web")]
21mod response;
22mod wire;
23
24use std::error::Error;
25
26use chrono::{DateTime, Utc};
27use serde::{Deserialize, Serialize};
28use serde_json::Value;
29use systemprompt_identifiers::TraceId;
30use systemprompt_traits::BoxedSource;
31
32#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub enum ErrorCode {
35 NotFound,
36 BadRequest,
37 Unauthorized,
38 Forbidden,
39 InternalError,
40 ValidationError,
41 ConflictError,
42 RateLimited,
43 ServiceUnavailable,
44}
45
46impl ErrorCode {
47 #[must_use]
48 pub const fn is_server_error(self) -> bool {
49 matches!(self, Self::InternalError | Self::ServiceUnavailable)
50 }
51
52 #[must_use]
53 pub const fn public_server_message(self) -> &'static str {
54 match self {
55 Self::ServiceUnavailable => "Service temporarily unavailable",
56 _ => "Internal server error",
57 }
58 }
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct ValidationError {
63 pub field: String,
64 pub message: String,
65 pub code: String,
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub context: Option<Value>,
69}
70
71#[derive(Debug, Deserialize)]
73pub struct ApiError {
74 pub code: ErrorCode,
75 pub message: String,
76 #[serde(default)]
77 pub details: Option<String>,
78 #[serde(default)]
79 pub error_key: Option<String>,
80 #[serde(default)]
81 pub path: Option<String>,
82 #[serde(default)]
83 pub validation_errors: Vec<ValidationError>,
84 pub timestamp: DateTime<Utc>,
85 #[serde(default)]
86 pub trace_id: Option<TraceId>,
87 #[serde(skip)]
88 source: Option<BoxedSource>,
89}
90
91impl ApiError {
92 pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
93 Self {
94 code,
95 message: message.into(),
96 details: None,
97 error_key: None,
98 path: None,
99 validation_errors: Vec::new(),
100 timestamp: Utc::now(),
101 trace_id: None,
102 source: None,
103 }
104 }
105
106 #[must_use]
107 pub fn with_details(mut self, details: impl Into<String>) -> Self {
108 self.details = Some(details.into());
109 self
110 }
111
112 #[must_use]
113 pub fn with_error_key(mut self, key: impl Into<String>) -> Self {
114 self.error_key = Some(key.into());
115 self
116 }
117
118 #[must_use]
119 pub fn with_path(mut self, path: impl Into<String>) -> Self {
120 self.path = Some(path.into());
121 self
122 }
123
124 #[must_use]
125 pub fn with_validation_errors(mut self, errors: Vec<ValidationError>) -> Self {
126 self.validation_errors = errors;
127 self
128 }
129
130 #[must_use]
131 pub fn with_trace_id(mut self, id: TraceId) -> Self {
132 self.trace_id = Some(id);
133 self
134 }
135
136 #[must_use]
137 pub fn with_source(mut self, source: impl Into<BoxedSource>) -> Self {
138 self.source = Some(source.into());
139 self
140 }
141
142 pub fn source(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
143 self.source.as_deref()
144 }
145
146 pub fn not_found(message: impl Into<String>) -> Self {
147 Self::new(ErrorCode::NotFound, message)
148 }
149
150 pub fn bad_request(message: impl Into<String>) -> Self {
151 Self::new(ErrorCode::BadRequest, message)
152 }
153
154 pub fn unauthorized(message: impl Into<String>) -> Self {
155 Self::new(ErrorCode::Unauthorized, message)
156 }
157
158 pub fn forbidden(message: impl Into<String>) -> Self {
159 Self::new(ErrorCode::Forbidden, message)
160 }
161
162 pub fn conflict(message: impl Into<String>) -> Self {
163 Self::new(ErrorCode::ConflictError, message)
164 }
165
166 pub fn rate_limited(message: impl Into<String>) -> Self {
167 Self::new(ErrorCode::RateLimited, message)
168 }
169
170 pub fn validation_error(message: impl Into<String>, errors: Vec<ValidationError>) -> Self {
171 Self::new(ErrorCode::ValidationError, message).with_validation_errors(errors)
172 }
173
174 pub fn internal_error(context: &'static str) -> Self {
175 Self::new(ErrorCode::InternalError, context)
176 }
177
178 pub fn internal(context: &'static str, source: impl Into<BoxedSource>) -> Self {
179 Self::internal_error(context).with_source(source)
180 }
181
182 pub fn service_unavailable(context: &'static str) -> Self {
183 Self::new(ErrorCode::ServiceUnavailable, context)
184 }
185}