systemprompt_traits/repository/
mod.rs1#[cfg(feature = "sqlx")]
18mod sqlx_classify;
19
20use std::fmt;
21
22pub type BoxedSource = Box<dyn std::error::Error + Send + Sync + 'static>;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ConstraintKind {
27 Unique,
28 Exclusion,
29 ForeignKey,
30 NotNull,
31 Check,
32}
33
34impl ConstraintKind {
35 #[must_use]
36 pub const fn code(self) -> &'static str {
37 match self {
38 Self::Unique => "unique_violation",
39 Self::Exclusion => "exclusion_violation",
40 Self::ForeignKey => "foreign_key_violation",
41 Self::NotNull => "not_null_violation",
42 Self::Check => "check_violation",
43 }
44 }
45
46 #[must_use]
47 pub const fn is_conflict(self) -> bool {
48 matches!(self, Self::Unique | Self::Exclusion | Self::ForeignKey)
49 }
50}
51
52impl fmt::Display for ConstraintKind {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 f.write_str(self.code())
55 }
56}
57
58#[derive(Debug, thiserror::Error)]
60#[non_exhaustive]
61pub enum RepositoryError {
62 #[error("{entity} not found{}", key_suffix(.key.as_deref()))]
63 NotFound {
64 entity: &'static str,
65 key: Option<String>,
66 },
67
68 #[error("{entity} {key} conflicts: {reason}")]
69 Conflict {
70 entity: &'static str,
71 key: String,
72 reason: String,
73 },
74
75 #[error("{kind} on constraint {constraint}")]
76 Constraint {
77 kind: ConstraintKind,
78 constraint: String,
79 #[source]
80 source: BoxedSource,
81 },
82
83 #[error("invalid argument {field}: {reason}")]
84 InvalidArgument { field: &'static str, reason: String },
85
86 #[error("invalid stored data in {field}: {reason}")]
87 InvalidData { field: &'static str, reason: String },
88
89 #[error("could not decode {context}")]
90 Decode {
91 context: String,
92 #[source]
93 source: BoxedSource,
94 },
95
96 #[error("database error: {source}")]
97 Database {
98 sqlstate: Option<String>,
99 #[source]
100 source: BoxedSource,
101 },
102
103 #[error("serialization error: {0}")]
104 Serialization(#[from] serde_json::Error),
105
106 #[error("SQL could not be split into statements: {0}")]
107 SqlParse(#[source] BoxedSource),
108
109 #[error("Failed to establish database connection")]
110 Connection(#[source] Box<Self>),
111
112 #[error("Failed to execute SQL statement: {statement}")]
113 Statement {
114 statement: String,
115 #[source]
116 source: Box<Self>,
117 },
118
119 #[error("Failed to read SQL file {path}")]
120 SqlFile {
121 path: String,
122 #[source]
123 source: std::io::Error,
124 },
125
126 #[error("transaction already committed or rolled back")]
127 TransactionConsumed,
128}
129
130impl RepositoryError {
131 pub fn not_found(entity: &'static str, key: impl fmt::Display) -> Self {
132 Self::NotFound {
133 entity,
134 key: Some(key.to_string()),
135 }
136 }
137
138 pub fn conflict(
139 entity: &'static str,
140 key: impl fmt::Display,
141 reason: impl Into<String>,
142 ) -> Self {
143 Self::Conflict {
144 entity,
145 key: key.to_string(),
146 reason: reason.into(),
147 }
148 }
149
150 pub fn invalid_argument(field: &'static str, reason: impl Into<String>) -> Self {
151 Self::InvalidArgument {
152 field,
153 reason: reason.into(),
154 }
155 }
156
157 pub fn invalid_data(field: &'static str, reason: impl Into<String>) -> Self {
158 Self::InvalidData {
159 field,
160 reason: reason.into(),
161 }
162 }
163
164 pub fn decode(context: impl Into<String>, source: impl Into<BoxedSource>) -> Self {
165 Self::Decode {
166 context: context.into(),
167 source: source.into(),
168 }
169 }
170
171 pub fn database(source: impl Into<BoxedSource>) -> Self {
172 let source: BoxedSource = source.into();
173 #[cfg(feature = "sqlx")]
174 let source = match source.downcast::<sqlx::Error>() {
175 Ok(sqlx_error) => return Self::from(*sqlx_error),
176 Err(other) => other,
177 };
178 Self::Database {
179 sqlstate: None,
180 source,
181 }
182 }
183
184 #[must_use]
185 pub fn sqlstate(&self) -> Option<&str> {
186 match self {
187 Self::Database { sqlstate, .. } => sqlstate.as_deref(),
188 Self::Statement { source, .. } | Self::Connection(source) => source.sqlstate(),
189 _ => None,
190 }
191 }
192
193 #[must_use]
194 pub const fn is_not_found(&self) -> bool {
195 matches!(self, Self::NotFound { .. })
196 }
197
198 #[must_use]
199 pub const fn is_constraint(&self) -> bool {
200 matches!(self, Self::Constraint { .. })
201 }
202
203 #[must_use]
204 pub const fn is_conflict(&self) -> bool {
205 match self {
206 Self::Conflict { .. } => true,
207 Self::Constraint { kind, .. } => kind.is_conflict(),
208 _ => false,
209 }
210 }
211
212 #[must_use]
213 pub fn is_serialization_failure(&self) -> bool {
214 matches!(self.sqlstate(), Some("40001" | "40P01"))
217 }
218
219 #[must_use]
220 pub fn is_invalid_function_definition(&self) -> bool {
221 self.sqlstate() == Some("42P13")
225 }
226}
227
228fn key_suffix(key: Option<&str>) -> String {
229 key.map(|key| format!(": {key}")).unwrap_or_default()
230}