|
2 | 2 | mod database_errors;
|
3 | 3 |
|
4 | 4 | use core::convert::From;
|
5 |
| -use serde::Deserialize; |
6 |
| -use std::fmt; |
| 5 | +use serde::{Deserialize, Serialize}; |
| 6 | +use std::{borrow::Cow, fmt}; |
7 | 7 | use thiserror::Error;
|
8 | 8 |
|
9 | 9 | /// A specialised result type for returning Thoth data
|
10 |
| -pub type ThothResult<T> = std::result::Result<T, ThothError>; |
| 10 | +pub type ThothResult<T> = Result<T, ThothError>; |
11 | 11 |
|
12 |
| -#[derive(Error, Debug, PartialEq, Eq)] |
| 12 | +#[derive(Error, Debug, PartialEq, Eq, Serialize, Deserialize)] |
13 | 13 | /// Represents anything that can go wrong in Thoth
|
14 | 14 | ///
|
15 | 15 | /// This type is not intended to be exhaustively matched, and new variants may
|
16 | 16 | /// be added in the future without a major version bump.
|
17 | 17 | pub enum ThothError {
|
18 |
| - #[error("{0} is not a valid {1} code")] |
19 |
| - InvalidSubjectCode(String, String), |
| 18 | + #[error("{input:?} is not a valid {subject_type:?} code")] |
| 19 | + InvalidSubjectCode { input: String, subject_type: String }, |
20 | 20 | #[error("Database error: {0}")]
|
21 | 21 | DatabaseError(String),
|
22 | 22 | #[error("Redis error: {0}")]
|
23 | 23 | RedisError(String),
|
24 | 24 | #[error("{0}")]
|
25 |
| - DatabaseConstraintError(&'static str), |
| 25 | + DatabaseConstraintError(Cow<'static, str>), |
26 | 26 | #[error("Internal error: {0}")]
|
27 | 27 | InternalError(String),
|
28 | 28 | #[error("Invalid credentials.")]
|
@@ -109,6 +109,18 @@ pub enum ThothError {
|
109 | 109 | ThothUpdateCanonicalError,
|
110 | 110 | }
|
111 | 111 |
|
| 112 | +impl ThothError { |
| 113 | + /// Serialise to JSON |
| 114 | + pub fn to_json(&self) -> ThothResult<String> { |
| 115 | + serde_json::to_string(&self).map_err(Into::into) |
| 116 | + } |
| 117 | + |
| 118 | + /// Deserialise from JSON |
| 119 | + pub fn from_json(s: &str) -> ThothResult<ThothError> { |
| 120 | + serde_json::from_str(s).map_err(Into::into) |
| 121 | + } |
| 122 | +} |
| 123 | + |
112 | 124 | #[cfg(not(target_arch = "wasm32"))]
|
113 | 125 | impl juniper::IntoFieldError for ThothError {
|
114 | 126 | fn into_field_error(self) -> juniper::FieldError {
|
@@ -293,6 +305,12 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for ThothError {
|
293 | 305 | }
|
294 | 306 | }
|
295 | 307 |
|
| 308 | +impl From<serde_json::Error> for ThothError { |
| 309 | + fn from(e: serde_json::Error) -> Self { |
| 310 | + ThothError::InternalError(e.to_string()) |
| 311 | + } |
| 312 | +} |
| 313 | + |
296 | 314 | #[cfg(test)]
|
297 | 315 | mod tests {
|
298 | 316 | use super::*;
|
@@ -331,4 +349,35 @@ mod tests {
|
331 | 349 | ThothError::GraphqlError("A relation with this ordinal already exists.".to_string())
|
332 | 350 | )
|
333 | 351 | }
|
| 352 | + |
| 353 | + #[test] |
| 354 | + fn test_round_trip_serialisation() { |
| 355 | + let original_error = ThothError::InvalidSubjectCode { |
| 356 | + input: "002".to_string(), |
| 357 | + subject_type: "BIC".to_string(), |
| 358 | + }; |
| 359 | + let json = original_error.to_json().unwrap(); |
| 360 | + let deserialised_error = ThothError::from_json(&json).unwrap(); |
| 361 | + assert_eq!(original_error, deserialised_error); |
| 362 | + } |
| 363 | + |
| 364 | + #[test] |
| 365 | + fn test_to_json_valid_error() { |
| 366 | + let error = ThothError::InvalidSubjectCode { |
| 367 | + input: "001".to_string(), |
| 368 | + subject_type: "BIC".to_string(), |
| 369 | + }; |
| 370 | + let json = error.to_json().unwrap(); |
| 371 | + |
| 372 | + assert!(json.contains("\"InvalidSubjectCode\"")); |
| 373 | + assert!(json.contains("\"001\"")); |
| 374 | + assert!(json.contains("\"BIC\"")); |
| 375 | + } |
| 376 | + |
| 377 | + #[test] |
| 378 | + fn test_invalid_json_deserialisation() { |
| 379 | + let invalid_json = r#"{"UnknownError":"Unexpected field"}"#; |
| 380 | + let error = ThothError::from_json(invalid_json); |
| 381 | + assert!(error.is_err()); |
| 382 | + } |
334 | 383 | }
|
0 commit comments