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