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