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}