Skip to main content

es_entity/
error.rs

1//! Types for working with errors produced by es-entity.
2
3use thiserror::Error;
4
5/// Error type for entity hydration failures (reconstructing entities from events).
6#[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)]
25/// Extracts the conflicting value from a PostgreSQL constraint violation detail message.
26///
27/// PostgreSQL formats unique violation details as:
28/// `Key (column)=(value) already exists.`
29///
30/// Returns `None` if the detail is missing or doesn't match the expected format.
31pub 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#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn parse_simple_uuid_value() {
48        let detail = Some("Key (id)=(550e8400-e29b-41d4-a716-446655440000) already exists.");
49        assert_eq!(
50            parse_constraint_detail_value(detail),
51            Some("550e8400-e29b-41d4-a716-446655440000".to_string())
52        );
53    }
54
55    #[test]
56    fn parse_string_value() {
57        let detail = Some("Key (email)=(user@example.com) already exists.");
58        assert_eq!(
59            parse_constraint_detail_value(detail),
60            Some("user@example.com".to_string())
61        );
62    }
63
64    #[test]
65    fn parse_composite_key_value() {
66        let detail = Some("Key (tenant_id, email)=(abc, user@example.com) already exists.");
67        assert_eq!(
68            parse_constraint_detail_value(detail),
69            Some("abc, user@example.com".to_string())
70        );
71    }
72
73    #[test]
74    fn parse_none_detail() {
75        assert_eq!(parse_constraint_detail_value(None), None);
76    }
77
78    #[test]
79    fn parse_unexpected_format() {
80        let detail = Some("something unexpected");
81        assert_eq!(parse_constraint_detail_value(detail), None);
82    }
83
84    #[test]
85    fn parse_value_containing_parentheses() {
86        let detail = Some("Key (name)=(foo (bar)) already exists.");
87        assert_eq!(
88            parse_constraint_detail_value(detail),
89            Some("foo (bar)".to_string())
90        );
91    }
92
93    #[test]
94    fn parse_empty_value() {
95        let detail = Some("Key (col)=() already exists.");
96        assert_eq!(parse_constraint_detail_value(detail), Some("".to_string()));
97    }
98}