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