grid_sdk/error/
constraint_violation.rs

1// Copyright 2018-2020 Cargill Incorporated
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Module containing ConstraintViolationError implementation.
16
17use std::error;
18use std::fmt;
19
20/// The type of constraint violation that caused the error
21///
22/// This is the type of constraint on the database's definition that is
23/// violated. For example, if an operation tries to insert an entry that would
24/// cause a duplicate in a unique column, the ConstraintViolationType::Unique
25/// should be used.
26#[derive(Debug)]
27pub enum ConstraintViolationType {
28    Unique,
29    ForeignKey,
30    Other(String),
31}
32
33impl fmt::Display for ConstraintViolationType {
34    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35        match &self {
36            ConstraintViolationType::Unique => write!(f, "Unique"),
37            ConstraintViolationType::ForeignKey => write!(f, "Foreign Key"),
38            ConstraintViolationType::Other(ref msg) => write!(f, "{}", msg),
39        }
40    }
41}
42
43/// An error which is returned because of a database constraint violation.
44///
45/// This error indicates that an update to a database failed because it would have violated
46/// a constraint defined as part of the database's definition. For example, if the database has
47/// a table with a unique column, and an attempt to insert an entry which would cause duplication in
48/// that column, an error with violation type `ConstraintViolationType::Unique` will occur.
49///
50/// Although this error maps closely to those generated by relational databases (such as those
51/// covered by Diesel), the underlying database does not need to be relational. It could, for
52/// example, be a memory or file-backed implementation of a store.
53pub struct ConstraintViolationError {
54    violation_type: ConstraintViolationType,
55    source: Option<Box<dyn error::Error>>,
56}
57
58impl ConstraintViolationError {
59    /// Constructs a new `ConstraintViolationError` from a specified violation type.
60    ///
61    /// The implementation of `std::fmt::Display` for this error will use the
62    /// standard display of the ConstraintViolationType for its message.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use grid_sdk::error::{ ConstraintViolationError, ConstraintViolationType };
68    ///
69    /// let constraint_violation_error = ConstraintViolationError::with_violation_type(
70    ///     ConstraintViolationType::Unique
71    /// );
72    /// assert_eq!(format!("{}", constraint_violation_error), "Unique constraint violated");
73    /// ```
74    pub fn with_violation_type(violation_type: ConstraintViolationType) -> Self {
75        Self {
76            violation_type,
77            source: None,
78        }
79    }
80
81    /// Constructs a new `ConstraintViolationError` from a specified source error and violation type.
82    ///
83    /// The implementation of `std::fmt::Display` for this error will simply pass through the
84    /// display of the source message unmodified.
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// use grid_sdk::error::{ ConstraintViolationError, ConstraintViolationType };
90    ///
91    /// let db_err = std::io::Error::new(std::io::ErrorKind::Other, "db error");
92    /// let constraint_violation_error = ConstraintViolationError::from_source_with_violation_type(
93    ///     ConstraintViolationType::Unique,
94    ///     Box::new(db_err)
95    /// );
96    /// assert_eq!(format!("{}", constraint_violation_error), "db error");
97    /// ```
98    pub fn from_source_with_violation_type(
99        violation_type: ConstraintViolationType,
100        source: Box<dyn error::Error>,
101    ) -> Self {
102        Self {
103            violation_type,
104            source: Some(source),
105        }
106    }
107}
108
109impl error::Error for ConstraintViolationError {
110    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
111        self.source.as_ref().map(|s| s.as_ref())
112    }
113}
114
115impl fmt::Display for ConstraintViolationError {
116    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
117        match &self.source {
118            Some(s) => write!(f, "{}", s),
119            None => write!(f, "{} constraint violated", &self.violation_type),
120        }
121    }
122}
123
124impl fmt::Debug for ConstraintViolationError {
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        const TYPE_NAME: &str = "ConstraintViolationError";
127
128        match &self.source {
129            Some(s) => write!(
130                f,
131                "{} {{ source: {:?}, violation_type: {:?} }}",
132                TYPE_NAME, s, &self.violation_type
133            ),
134            None => write!(
135                f,
136                "{} {{ violation_type: {:?} }}",
137                TYPE_NAME, &self.violation_type
138            ),
139        }
140    }
141}
142
143#[cfg(test)]
144pub mod tests {
145    use super::*;
146
147    /// Tests that errors constructed with `ConstraintViolationError::with_violation_type`
148    /// return a debug string of the form
149    /// `format!("ConstraintViolationError { violation_type: {:?} }", type)`.
150    #[test]
151    fn test_debug_with_violation_type() {
152        let debug = "ConstraintViolationError { violation_type: Unique }";
153        let err = ConstraintViolationError::with_violation_type(ConstraintViolationType::Unique);
154        assert_eq!(format!("{:?}", err), debug);
155    }
156
157    /// Tests that errors constructed with `ConstraintViolationError::from_source_with_violation_type`
158    /// return a debug string of the form
159    /// `format!("ConstraintViolationError { source: {:?}, violation_type: {:?} }", source, type)`.
160    #[test]
161    fn test_debug_from_source_with_violation_type() {
162        let debug = "ConstraintViolationError { source: ConstraintViolationError { violation_type: Unique }, violation_type: Unique }";
163        let err = ConstraintViolationError::from_source_with_violation_type(
164            ConstraintViolationType::Unique,
165            Box::new(ConstraintViolationError::with_violation_type(
166                ConstraintViolationType::Unique,
167            )),
168        );
169        assert_eq!(format!("{:?}", err), debug);
170    }
171
172    /// Tests that error constructed with `ConstraintViolationError::with_violation_type`
173    /// return a display string which specifies the which constraint type was violated.
174    #[test]
175    fn test_display_with_violation_type() {
176        let disp = "Unique constraint violated";
177        let err = ConstraintViolationError::with_violation_type(ConstraintViolationType::Unique);
178        assert_eq!(format!("{}", err), disp);
179    }
180
181    /// Tests that error constructed with `ConstraintViolationError::from_source_with_violation_type`
182    /// return a display string which specifies the source's display string.
183    #[test]
184    fn test_display_from_source_with_violation_type() {
185        let disp = "Unique constraint violated";
186        let err = ConstraintViolationError::from_source_with_violation_type(
187            ConstraintViolationType::Unique,
188            Box::new(ConstraintViolationError::with_violation_type(
189                ConstraintViolationType::Unique,
190            )),
191        );
192        assert_eq!(format!("{}", err), disp);
193    }
194}