1use thiserror::Error;
4
5#[derive(Error, Debug)]
7pub enum EntityHydrationError {
8 #[error("EntityHydrationError - UninitializedFieldError: {0}")]
9 UninitializedFieldError(#[from] derive_builder::UninitializedFieldError),
10 #[error("EntityHydrationError - Deserialization: {0}")]
11 EventDeserialization(#[from] serde_json::Error),
12}
13
14#[derive(Error, Debug)]
15#[error("CursorDestructureError: couldn't turn {0} into {1}")]
16pub struct CursorDestructureError(&'static str, &'static str);
17
18impl From<(&'static str, &'static str)> for CursorDestructureError {
19 fn from((name, variant): (&'static str, &'static str)) -> Self {
20 Self(name, variant)
21 }
22}
23
24#[doc(hidden)]
25pub fn parse_constraint_detail_value(detail: Option<&str>) -> Option<String> {
32 let detail = detail?;
33 let start = detail.find("=(")? + 2;
34 let end = detail.rfind(") already")?;
35 if start <= end {
36 Some(detail[start..end].to_string())
37 } else {
38 None
39 }
40}
41
42#[doc(hidden)]
43pub fn extract_constraint_value(db_err: &dyn sqlx::error::DatabaseError) -> Option<String> {
48 let pg_err = db_err.try_downcast_ref::<sqlx::postgres::PgDatabaseError>()?;
49 parse_constraint_detail_value(pg_err.detail())
50}
51
52#[doc(hidden)]
53pub struct NotFoundValue<'a, T: ?Sized>(pub &'a T);
56
57impl<T: std::fmt::Display + ?Sized> NotFoundValue<'_, T> {
58 pub fn to_not_found_value(&self) -> String {
59 self.0.to_string()
60 }
61}
62
63#[doc(hidden)]
64pub trait ToNotFoundValueFallback {
65 fn to_not_found_value(&self) -> String;
66}
67
68impl<T: std::fmt::Debug + ?Sized> ToNotFoundValueFallback for NotFoundValue<'_, T> {
69 fn to_not_found_value(&self) -> String {
70 format!("{:?}", self.0)
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn parse_simple_uuid_value() {
80 let detail = Some("Key (id)=(550e8400-e29b-41d4-a716-446655440000) already exists.");
81 assert_eq!(
82 parse_constraint_detail_value(detail),
83 Some("550e8400-e29b-41d4-a716-446655440000".to_string())
84 );
85 }
86
87 #[test]
88 fn parse_string_value() {
89 let detail = Some("Key (email)=(user@example.com) already exists.");
90 assert_eq!(
91 parse_constraint_detail_value(detail),
92 Some("user@example.com".to_string())
93 );
94 }
95
96 #[test]
97 fn parse_composite_key_value() {
98 let detail = Some("Key (tenant_id, email)=(abc, user@example.com) already exists.");
99 assert_eq!(
100 parse_constraint_detail_value(detail),
101 Some("abc, user@example.com".to_string())
102 );
103 }
104
105 #[test]
106 fn parse_none_detail() {
107 assert_eq!(parse_constraint_detail_value(None), None);
108 }
109
110 #[test]
111 fn parse_unexpected_format() {
112 let detail = Some("something unexpected");
113 assert_eq!(parse_constraint_detail_value(detail), None);
114 }
115
116 #[test]
117 fn parse_value_containing_parentheses() {
118 let detail = Some("Key (name)=(foo (bar)) already exists.");
119 assert_eq!(
120 parse_constraint_detail_value(detail),
121 Some("foo (bar)".to_string())
122 );
123 }
124
125 #[test]
126 fn parse_empty_value() {
127 let detail = Some("Key (col)=() already exists.");
128 assert_eq!(parse_constraint_detail_value(detail), Some("".to_string()));
129 }
130
131 #[test]
132 fn not_found_value_uses_display_when_available() {
133 #[allow(unused_imports)]
134 use crate::ToNotFoundValueFallback;
135
136 let val = "hello";
138 assert_eq!(NotFoundValue(val).to_not_found_value(), "hello");
139
140 let num = 42;
142 assert_eq!(NotFoundValue(&num).to_not_found_value(), "42");
143 }
144
145 #[test]
146 fn not_found_value_falls_back_to_debug() {
147 use crate::ToNotFoundValueFallback;
148
149 #[derive(Debug)]
151 #[allow(dead_code)]
152 struct OnlyDebug(i32);
153
154 let val = OnlyDebug(7);
155 assert_eq!(NotFoundValue(&val).to_not_found_value(), "OnlyDebug(7)");
156 }
157}