Skip to main content

es_entity/
constraint.rs

1use std::{error::Error, fmt, sync::Arc};
2
3use crate::ConstraintKind;
4
5/// Structured diagnostics. Attempted values are deliberately absent from Display.
6#[derive(Debug, Clone)]
7pub struct ConstraintDiagnostics {
8    pub table: &'static str,
9    pub constraint: &'static str,
10    pub kind: ConstraintKind,
11    source: Arc<sqlx::Error>,
12}
13impl fmt::Display for ConstraintDiagnostics {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        write!(
16            f,
17            "constraint violation on {} ({})",
18            self.table, self.constraint
19        )
20    }
21}
22impl Error for ConstraintDiagnostics {
23    fn source(&self) -> Option<&(dyn Error + 'static)> {
24        Some(self.source.as_ref())
25    }
26}
27
28/// A known constraint and the typed write input, when it can be attributed.
29/// Values may contain PII. Inspect deliberately; default Display never prints them.
30#[derive(Debug, Clone)]
31pub struct ConstraintConflict<V> {
32    pub attempted: Option<V>,
33    pub diagnostics: ConstraintDiagnostics,
34}
35impl<V> ConstraintConflict<V> {
36    #[doc(hidden)]
37    pub fn new(
38        attempted: Option<V>,
39        table: &'static str,
40        constraint: &'static str,
41        kind: ConstraintKind,
42        source: sqlx::Error,
43    ) -> Self {
44        Self {
45            attempted,
46            diagnostics: ConstraintDiagnostics {
47                table,
48                constraint,
49                kind,
50                source: Arc::new(source),
51            },
52        }
53    }
54}
55impl<V> fmt::Display for ConstraintConflict<V> {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        self.diagnostics.fmt(f)
58    }
59}
60impl<V: fmt::Debug> Error for ConstraintConflict<V> {
61    fn source(&self) -> Option<&(dyn Error + 'static)> {
62        Some(&self.diagnostics)
63    }
64}
65
66/// The primary-key conflict of a create: the id is always known, from the
67/// write's own input (a single create) or by matching the database's
68/// reported key against the batch's own ids (`create_all`). Unlike
69/// [`ConstraintConflict`], `attempted` is never optional here — a pkey
70/// violation with no attributable id is `Fatal(Invariant)` instead, never
71/// this variant with a guessed or missing id.
72#[derive(Debug, Clone)]
73pub struct IdConflict<Id> {
74    pub attempted: Id,
75    pub diagnostics: ConstraintDiagnostics,
76}
77impl<Id> IdConflict<Id> {
78    #[doc(hidden)]
79    pub fn new(
80        attempted: Id,
81        table: &'static str,
82        constraint: &'static str,
83        kind: ConstraintKind,
84        source: sqlx::Error,
85    ) -> Self {
86        Self {
87            attempted,
88            diagnostics: ConstraintDiagnostics {
89                table,
90                constraint,
91                kind,
92                source: Arc::new(source),
93            },
94        }
95    }
96}
97impl<Id> fmt::Display for IdConflict<Id> {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        self.diagnostics.fmt(f)
100    }
101}
102impl<Id: fmt::Debug> Error for IdConflict<Id> {
103    fn source(&self) -> Option<&(dyn Error + 'static)> {
104        Some(&self.diagnostics)
105    }
106}