@@ -0,0 +1,140 @@ |
| 1 | +#![allow(dead_code)] |
| 2 | + |
| 3 | +use crate::request::RequestError; |
| 4 | + |
| 5 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 6 | +#[repr(u32)] |
| 7 | +pub enum PortalResponseCode { |
| 8 | + Success = 0, |
| 9 | + Cancelled = 1, |
| 10 | + Failed = 2, |
| 11 | +} |
| 12 | + |
| 13 | +#[derive(Debug, Clone, PartialEq, Eq)] |
| 14 | +pub enum PortalError { |
| 15 | + CancelledByUser, |
| 16 | + InvalidRequestPayload, |
| 17 | + InvalidParentWindow, |
| 18 | + OwnershipMismatch, |
| 19 | + RequestNotFound, |
| 20 | + RequestAlreadyExists, |
| 21 | + RequestTimeout, |
| 22 | + InvalidTransition, |
| 23 | + InternalFailure, |
| 24 | +} |
| 25 | + |
| 26 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 27 | +pub struct ErrorMapping { |
| 28 | + pub code: PortalResponseCode, |
| 29 | + pub reason: &'static str, |
| 30 | +} |
| 31 | + |
| 32 | +#[must_use] |
| 33 | +pub fn map_portal_error(error: &PortalError) -> ErrorMapping { |
| 34 | + match error { |
| 35 | + PortalError::CancelledByUser => ErrorMapping { |
| 36 | + code: PortalResponseCode::Cancelled, |
| 37 | + reason: "cancelled", |
| 38 | + }, |
| 39 | + PortalError::InvalidRequestPayload => ErrorMapping { |
| 40 | + code: PortalResponseCode::Failed, |
| 41 | + reason: "invalid_request", |
| 42 | + }, |
| 43 | + PortalError::InvalidParentWindow => ErrorMapping { |
| 44 | + code: PortalResponseCode::Failed, |
| 45 | + reason: "invalid_parent_window", |
| 46 | + }, |
| 47 | + PortalError::OwnershipMismatch => ErrorMapping { |
| 48 | + code: PortalResponseCode::Failed, |
| 49 | + reason: "ownership_mismatch", |
| 50 | + }, |
| 51 | + PortalError::RequestNotFound => ErrorMapping { |
| 52 | + code: PortalResponseCode::Failed, |
| 53 | + reason: "request_not_found", |
| 54 | + }, |
| 55 | + PortalError::RequestAlreadyExists => ErrorMapping { |
| 56 | + code: PortalResponseCode::Failed, |
| 57 | + reason: "request_conflict", |
| 58 | + }, |
| 59 | + PortalError::RequestTimeout => ErrorMapping { |
| 60 | + code: PortalResponseCode::Failed, |
| 61 | + reason: "request_timeout", |
| 62 | + }, |
| 63 | + PortalError::InvalidTransition => ErrorMapping { |
| 64 | + code: PortalResponseCode::Failed, |
| 65 | + reason: "invalid_transition", |
| 66 | + }, |
| 67 | + PortalError::InternalFailure => ErrorMapping { |
| 68 | + code: PortalResponseCode::Failed, |
| 69 | + reason: "internal_error", |
| 70 | + }, |
| 71 | + } |
| 72 | +} |
| 73 | + |
| 74 | +#[must_use] |
| 75 | +pub fn map_request_error(error: &RequestError) -> ErrorMapping { |
| 76 | + match error { |
| 77 | + RequestError::AlreadyExists(_) => map_portal_error(&PortalError::RequestAlreadyExists), |
| 78 | + RequestError::NotFound(_) => map_portal_error(&PortalError::RequestNotFound), |
| 79 | + RequestError::OwnerMismatch(_) => map_portal_error(&PortalError::OwnershipMismatch), |
| 80 | + RequestError::InvalidTransition { .. } => map_portal_error(&PortalError::InvalidTransition), |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +#[cfg(test)] |
| 85 | +mod tests { |
| 86 | + use super::{PortalError, PortalResponseCode, map_portal_error, map_request_error}; |
| 87 | + use crate::request::{RequestError, RequestState}; |
| 88 | + |
| 89 | + #[test] |
| 90 | + fn cancelled_maps_to_cancelled_code() { |
| 91 | + let mapping = map_portal_error(&PortalError::CancelledByUser); |
| 92 | + assert_eq!(mapping.code, PortalResponseCode::Cancelled); |
| 93 | + assert_eq!(mapping.reason, "cancelled"); |
| 94 | + assert_eq!(mapping.code as u32, 1); |
| 95 | + } |
| 96 | + |
| 97 | + #[test] |
| 98 | + fn request_owner_mismatch_maps_to_failed_code() { |
| 99 | + let mapping = map_request_error(&RequestError::OwnerMismatch("req-1".to_string())); |
| 100 | + assert_eq!(mapping.code, PortalResponseCode::Failed); |
| 101 | + assert_eq!(mapping.reason, "ownership_mismatch"); |
| 102 | + assert_eq!(mapping.code as u32, 2); |
| 103 | + } |
| 104 | + |
| 105 | + #[test] |
| 106 | + fn invalid_transition_maps_to_stable_reason() { |
| 107 | + let mapping = map_request_error(&RequestError::InvalidTransition { |
| 108 | + id: "req-1".to_string(), |
| 109 | + from: RequestState::Pending, |
| 110 | + to: RequestState::Fulfilled, |
| 111 | + }); |
| 112 | + assert_eq!(mapping.code, PortalResponseCode::Failed); |
| 113 | + assert_eq!(mapping.reason, "invalid_transition"); |
| 114 | + } |
| 115 | + |
| 116 | + #[test] |
| 117 | + fn all_portal_error_variants_are_mapped() { |
| 118 | + let errors = [ |
| 119 | + PortalError::CancelledByUser, |
| 120 | + PortalError::InvalidRequestPayload, |
| 121 | + PortalError::InvalidParentWindow, |
| 122 | + PortalError::OwnershipMismatch, |
| 123 | + PortalError::RequestNotFound, |
| 124 | + PortalError::RequestAlreadyExists, |
| 125 | + PortalError::RequestTimeout, |
| 126 | + PortalError::InvalidTransition, |
| 127 | + PortalError::InternalFailure, |
| 128 | + ]; |
| 129 | + |
| 130 | + for error in errors { |
| 131 | + let mapping = map_portal_error(&error); |
| 132 | + assert!(!mapping.reason.is_empty()); |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + #[test] |
| 137 | + fn success_code_constant_stays_zero() { |
| 138 | + assert_eq!(PortalResponseCode::Success as u32, 0); |
| 139 | + } |
| 140 | +} |