//! `MxStatus` — 4-tuple `(Success, Category, DetectedBy, Detail)` per //! `src/MxNativeCodec/MxStatus.cs:28-65`. //! //! `Success=-1` is the documented OK sentinel. Detail is a signed 16-bit //! lookup code; canonical text for known codes is in [`detail_text`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[non_exhaustive] #[repr(i16)] pub enum MxStatusCategory { #[default] Unknown = -1, Ok = 0, Pending = 1, Warning = 2, CommunicationError = 3, ConfigurationError = 4, OperationalError = 5, SecurityError = 6, SoftwareError = 7, OtherError = 8, } impl MxStatusCategory { pub fn from_i16(value: i16) -> Self { match value { 0 => Self::Ok, 1 => Self::Pending, 2 => Self::Warning, 3 => Self::CommunicationError, 4 => Self::ConfigurationError, 5 => Self::OperationalError, 6 => Self::SecurityError, 7 => Self::SoftwareError, 8 => Self::OtherError, _ => Self::Unknown, } } pub fn to_i16(self) -> i16 { self as i16 } } /// Seven values per `MxStatus.cs:17-26`. The `DetectedBy` field is essential /// for diagnostics — it identifies which layer detected the fault. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[non_exhaustive] #[repr(i16)] pub enum MxStatusSource { #[default] Unknown = -1, RequestingLmx = 0, RespondingLmx = 1, RequestingNmx = 2, RespondingNmx = 3, RequestingAutomationObject = 4, RespondingAutomationObject = 5, } impl MxStatusSource { pub fn from_i16(value: i16) -> Self { match value { 0 => Self::RequestingLmx, 1 => Self::RespondingLmx, 2 => Self::RequestingNmx, 3 => Self::RespondingNmx, 4 => Self::RequestingAutomationObject, 5 => Self::RespondingAutomationObject, _ => Self::Unknown, } } pub fn to_i16(self) -> i16 { self as i16 } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct MxStatus { pub success: i16, pub category: MxStatusCategory, pub detected_by: MxStatusSource, pub detail: i16, } impl MxStatus { /// `(success=-1, Ok, RequestingLmx, detail=0)` — `MxStatus.DataChangeOk` /// from `MxStatus.cs:36-40`. pub const DATA_CHANGE_OK: Self = Self { success: -1, category: MxStatusCategory::Ok, detected_by: MxStatusSource::RequestingLmx, detail: 0, }; /// `(success=-1, Ok, RespondingAutomationObject, detail=0)` — /// `MxStatus.WriteCompleteOk` from `MxStatus.cs:42-46`. pub const WRITE_COMPLETE_OK: Self = Self { success: -1, category: MxStatusCategory::Ok, detected_by: MxStatusSource::RespondingAutomationObject, detail: 0, }; /// `(success=-1, Pending, RequestingLmx, detail=0)` — /// `MxStatus.SuspendPending` from `MxStatus.cs:48-52`. pub const SUSPEND_PENDING: Self = Self { success: -1, category: MxStatusCategory::Pending, detected_by: MxStatusSource::RequestingLmx, detail: 0, }; /// `(success=-1, Ok, RequestingLmx, detail=0)` — `MxStatus.ActivateOk` /// from `MxStatus.cs:54-58`. pub const ACTIVATE_OK: Self = Self { success: -1, category: MxStatusCategory::Ok, detected_by: MxStatusSource::RequestingLmx, detail: 0, }; /// `(success=0, ConfigurationError, RequestingLmx, detail=6)` — /// `MxStatus.InvalidReferenceConfiguration` from `MxStatus.cs:60-64`. pub const INVALID_REFERENCE_CONFIGURATION: Self = Self { success: 0, category: MxStatusCategory::ConfigurationError, detected_by: MxStatusSource::RequestingLmx, detail: 6, }; /// Look up the canonical text for `self.detail`, mirroring /// `MxStatus.DetailText` (`MxStatus.cs:34`). Returns `None` for unknown /// detail codes. pub fn detail_text(&self) -> Option<&'static str> { detail_text(self.detail) } pub fn is_ok(&self) -> bool { self.category == MxStatusCategory::Ok } } /// Canonical detail-code text per `MxStatusDetails.KnownDetails` /// (`MxStatus.cs:69-120`). Returns `None` for unknown codes. pub fn detail_text(detail: i16) -> Option<&'static str> { match detail { 16 => Some("Request timed out"), 17 => Some("Platform communication error"), 18 => Some("Invalid platform ID"), 19 => Some("Invalid engine ID"), 20 => Some("Engine communication error"), 21 => Some("Invalid reference"), 22 => Some("No Galaxy Repository"), 23 => Some("Invalid object ID"), 24 => Some("Object signature mismatch"), 25 => Some("Invalid primitive ID"), 26 => Some("Invalid attribute ID"), 27 => Some("Invalid property ID"), 28 => Some("Index out of range"), 29 => Some("Data out of range"), 30 => Some("Incorrect data type"), 31 => Some("Attribute not readable"), 32 => Some("Attribute not writeable"), 33 => Some("Write access denied"), 34 => Some("Unknown error"), 35 => Some("detected by"), 36 => Some("Wrong data type"), 37 => Some("Wrong number of dimensions"), 38 => Some("Invalid index"), 39 => Some("Index out of order"), 40 => Some("Dimension does not exist"), 41 => Some("Conversion not supported"), 42 => Some("Unable to convert string"), 43 => Some("Overflow"), 44 => Some("Attribute signature mismatch"), 45 => Some("Resolving local portion of reference"), 46 => Some("Resolving global portion of reference"), 47 => Some("Nmx version mismatch"), 48 => Some("Nmx command not valid"), 49 => Some("Lmx version mismatch"), 50 => Some("Lmx command not valid"), 51 => Some( "However, the object could not be put On Scan - Permission to modify \"Operate\" attributes is required", ), 52 => Some( "Unable to resolve reference for 'set' request because Galaxy Repository is busy performing a 'Deploy/Undeploy' operation", ), 53 => Some("Too many outstanding pending requests to engine"), 54 => Some("Object Initializing"), 55 => Some("Engine Initializing"), 56 => Some("Secured Write"), 57 => Some("Verified Write"), 58 => Some("No Alarm Ack Privilege"), 59 => Some("Alarm Acked Already"), 60 => Some("User did not have the necessary permissions to write"), 61 => Some("Verifier did not have the necessary permissions to verify"), 541 => Some("Conversion to intended data type is not supported"), 542 => Some("Unable to convert the input string to intended data type"), 8017 => Some( "Object must be offscan to modify attributes that have an MxSecurityConfigure security classification", ), _ => None, } } #[cfg(test)] #[allow(clippy::unwrap_used, clippy::expect_used)] mod tests { use super::*; #[test] fn category_round_trip() { for cat in [ MxStatusCategory::Unknown, MxStatusCategory::Ok, MxStatusCategory::Pending, MxStatusCategory::Warning, MxStatusCategory::CommunicationError, MxStatusCategory::ConfigurationError, MxStatusCategory::OperationalError, MxStatusCategory::SecurityError, MxStatusCategory::SoftwareError, MxStatusCategory::OtherError, ] { assert_eq!(MxStatusCategory::from_i16(cat.to_i16()), cat); } } #[test] fn source_round_trip() { for src in [ MxStatusSource::Unknown, MxStatusSource::RequestingLmx, MxStatusSource::RespondingLmx, MxStatusSource::RequestingNmx, MxStatusSource::RespondingNmx, MxStatusSource::RequestingAutomationObject, MxStatusSource::RespondingAutomationObject, ] { assert_eq!(MxStatusSource::from_i16(src.to_i16()), src); } } #[test] fn unknown_codes_map_to_unknown_variants() { assert_eq!(MxStatusCategory::from_i16(99), MxStatusCategory::Unknown); assert_eq!(MxStatusCategory::from_i16(-99), MxStatusCategory::Unknown); assert_eq!(MxStatusSource::from_i16(99), MxStatusSource::Unknown); assert_eq!(MxStatusSource::from_i16(-2), MxStatusSource::Unknown); } #[test] fn canonical_sentinels_match_dotnet() { // `MxStatus.cs:36-58` defines five canonical sentinels. assert_eq!(MxStatus::DATA_CHANGE_OK.success, -1); assert_eq!(MxStatus::DATA_CHANGE_OK.category, MxStatusCategory::Ok); assert_eq!( MxStatus::DATA_CHANGE_OK.detected_by, MxStatusSource::RequestingLmx ); assert_eq!(MxStatus::DATA_CHANGE_OK.detail, 0); assert_eq!( MxStatus::WRITE_COMPLETE_OK.detected_by, MxStatusSource::RespondingAutomationObject ); assert_eq!( MxStatus::INVALID_REFERENCE_CONFIGURATION.success, 0, "InvalidReferenceConfiguration uses success=0, not -1" ); assert_eq!(MxStatus::INVALID_REFERENCE_CONFIGURATION.detail, 6); } #[test] fn detail_text_known_codes() { assert_eq!(detail_text(16), Some("Request timed out")); assert_eq!(detail_text(21), Some("Invalid reference")); assert_eq!(detail_text(33), Some("Write access denied")); assert_eq!(detail_text(57), Some("Verified Write")); assert_eq!( detail_text(541), Some("Conversion to intended data type is not supported") ); assert_eq!( detail_text(8017), Some( "Object must be offscan to modify attributes that have an MxSecurityConfigure security classification" ) ); } #[test] fn detail_text_unknown_codes() { assert_eq!(detail_text(0), None); assert_eq!(detail_text(15), None); assert_eq!(detail_text(62), None); assert_eq!(detail_text(540), None); assert_eq!(detail_text(8018), None); assert_eq!(detail_text(-1), None); } #[test] fn is_ok_categorisation() { assert!(MxStatus::DATA_CHANGE_OK.is_ok()); assert!(MxStatus::WRITE_COMPLETE_OK.is_ok()); assert!(MxStatus::ACTIVATE_OK.is_ok()); assert!(!MxStatus::SUSPEND_PENDING.is_ok()); assert!(!MxStatus::INVALID_REFERENCE_CONFIGURATION.is_ok()); } }