labview_interop/
errors.rs

1//! # Error handling in LabVIEW Interop
2//!
3//! There are four error paths that the labview-interop crate needs to handle:
4//!
5//! 1. **MgError and MgErrorCode**, Internal: LabView Memory Manager --> Rust
6//!    This crate calls the labview memory manager internally to deal with memory
7//!    owned by LabVIEW. The functions of the memory manager return MgErr. The documentation
8//!    on https://www.ni.com/docs/en-US/bundle/labview/page/labview-manager-function-errors.html
9//!    gives a full list of possible error values.
10//!
11//! 2. **MgErrorCode**: to LabVIEW through function return
12//!    We want to be able to return the errors generated internally through the function return and be
13//!    understood on the LabVIEW side. This is straight forward for Errors of MgError type. But we will
14//!    have an internal compound Error type that can have a different type. When using status returns, these
15//!    can only be converted to a very generic error code. Therefore 3
16//!
17//! 3. **InteropError**: to LabVIEW through ErrorCluster parameter
18//!    Our internal `LvInteropError` compound error can easily be converted to an ErrorCluster. For MgErrors the conversion is
19//!    straight forward. The correct source descriptions are gotten from the memory manager through `NIGetOneErrorCode`.
20//!    For non LV errors, a generic error is leveraged, and the source description is overwritten.
21//!
22//! 4. from LabVIEW through ErrorCluster parameter
23//!    Will labview-interop ever need to make sense of an error? It may be good enough to differentiate between an error and
24//!    warnings. TBD
25//!
26//! # Notes on Error Handling in LabVIEW
27//! This section is a summary of defined and observed LabVIEW behaviour
28//!
29//! ## Labview error clusters and data types
30//! THe labview error clusters possess an error code of i32. The error lists on labviewwiki show
31//! official Labview errors as low as 0x8000000A and as high as 0x3FFC0105.
32//!
33//! ## the Labview Memory Manager / MgErr and types
34//! The memory manager code examples from the documentation call the return value of the c function calls ´MgErr´ of type i32
35//!
36//! ## Custom error ranges
37//! Custom defined errors can range from
38//! * -8999 through -8000
39//! * 5000 through 9999
40//! * 500,000 through 599,999
41//!
42//! For obvious reasons the labview interop crate will use the **range 542,000 to 542,999** for errors that are generated
43//! internally and not handed down by c functions
44//!
45//! # Error Implementation
46//! There is a hierarchy of Status and Errors
47//! - A status encode Success and Error
48//!
49//! The errors we expect to receive from calls to labview functions are
50//! MgErrorCode
51//! MgError
52//!
53//! Our generic Error handling is an enum
54//! LabviewInteropError
55//! This enum has custom errors for our internal use, we can hold MgErrors, and as last resort we can also hold
56//! LVError
57
58use crate::types::LVStatusCode;
59use num_enum::{IntoPrimitive, TryFromPrimitive};
60use thiserror::Error;
61
62/// the conversion from LVInteropError back to LVStatusCode is important
63/// to return the status in extern "C" functions back to LV
64impl<T> From<Result<T>> for LVStatusCode {
65    fn from(value: Result<T>) -> Self {
66        match value {
67            Ok(_) => LVStatusCode::SUCCESS,
68            Err(err) => (&err).into(),
69        }
70    }
71}
72impl From<&LVInteropError> for LVStatusCode {
73    fn from(value: &LVInteropError) -> Self {
74        match value {
75            LVInteropError::LabviewMgError(e) => e.into(),
76            LVInteropError::InternalError(e) => e.into(),
77            LVInteropError::LabviewError(e) => *e,
78        }
79    }
80}
81
82impl From<LVInteropError> for LVStatusCode {
83    fn from(value: LVInteropError) -> Self {
84        (&value).into()
85    }
86}
87
88impl From<LVStatusCode> for LVInteropError {
89    fn from(status: LVStatusCode) -> Self {
90        LVInteropError::LabviewError(status)
91    }
92}
93
94/// MgError is the subset of LabVIEW errors that may occur when dealing with the memory manager
95/// So in the context of Rust-LabVIEW-interop these are the kind of labview errors we may trigger within the library
96///
97///
98/// The `MgError` / `MgErrorCode` implement From in both directions. Additionally IntoPrimitive and TryFromPrimitive is derived
99/// to enable the conversion from and to int primitives.
100///
101/// LabVIEW official general error list:
102/// https://www.ni.com/docs/en-US/bundle/labview-api-ref/page/errors/general-labview-error-codes.html
103/// more complete inofficial lists:
104/// https://labviewwiki.org/wiki/LabVIEW_Error_Code_Family   /  https://labviewwiki.org/wiki/List_of_errors
105///
106/// Accoring to https://www.ni.com/docs/en-US/bundle/labview/page/labview-manager-function-errors.html
107/// the memory manager only uses a subset of this huge error list. The subset is implemented in `MgError` using
108/// the official abbreviations.
109///
110/// in https://www.ni.com/docs/en-US/bundle/labview/page/labview-manager-function-errors.html
111/// `MgError` implements Error on top of the `MgErrorCode` and includes a description
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Error, IntoPrimitive, TryFromPrimitive)]
113#[repr(i32)]
114pub enum MgError {
115    #[error("An input parameter is invalid.")]
116    MgArgErr = 1,
117    #[error("Memory is full.")]
118    MFullErr = 2,
119    #[error("End of file encountered.")]
120    FEof = 4,
121    #[error("File already open")]
122    FIsOpen = 5,
123    #[error("Generic file I/O error.")]
124    FIoErr = 6,
125    #[error("File not found")]
126    FNotFound = 7,
127    #[error("File permission error.")]
128    FNoPerm = 8,
129    #[error("Disk full")]
130    FDiskFull = 9,
131    #[error("Duplicate path")]
132    FDupPath = 10,
133    #[error("Too many files open.")]
134    FtMFOpen = 11,
135    #[error("Some system capacity necessary for operation is not enabled.")]
136    FNotEnabled = 12,
137    #[error(
138                "Failed to load dynamic library because of missing external symbols or dependencies, or because of an invalid file format."
139            )]
140    RfNotFound = 13,
141    #[error("Cannot add resource.")]
142    RAddFailed = 14,
143    #[error("Resource not found.")]
144    RNotFound = 15,
145    #[error("Image not found.")]
146    INotFound = 16,
147    #[error("Not enough memory to manipulate image.")]
148    IMemoryErr = 17,
149    #[error("DPen does not exist.")]
150    DPenNotExist = 18,
151    #[error("Configuration type invalid.")]
152    CfgBadType = 19,
153    #[error("Configuration token not found.")]
154    CfgTokenNotFound = 20,
155    #[error("Error occurred parsing configuration string.")]
156    CfgParseError = 21,
157    #[error("Configuration memory error.")]
158    CfgAllocError = 22,
159    #[error("Bad external code format.")]
160    EcLVSBFormatError = 23,
161    #[error("External subroutine not supported.")]
162    EcLVSBSubrError = 24,
163    #[error("External code not present.")]
164    EcLVSBNoCodeError = 25,
165    #[error("Null window.")]
166    WNullWindow = 26,
167    #[error("Destroy window error.")]
168    WDestroyMixup = 27,
169    #[error("Null menu.")]
170    MenuNullMenu = 28,
171    #[error("Print aborted")]
172    PAbortJob = 29,
173    #[error("Bad print record.")]
174    PBadPrintRecord = 30,
175    #[error("Print driver error.")]
176    PDriverError = 31,
177    #[error("Operating system error during print.")]
178    PWindowsError = 32,
179    #[error("Memory error during print.")]
180    PMemoryError = 33,
181    #[error("Print dialog error.")]
182    PDialogError = 34,
183    #[error("Generic print error.")]
184    PMiscError = 35,
185    #[error("Invalid device refnum.")]
186    DvInvalidRefnum = 36,
187    #[error("Device not found.")]
188    DvDeviceNotFound = 37,
189    #[error("Device parameter error.")]
190    DvParamErr = 38,
191    #[error("Device unit error.")]
192    DvUnitErr = 39,
193    #[error("Cannot open device.")]
194    DvOpenErr = 40,
195    #[error("Device call aborted.")]
196    DvAbortErr = 41,
197    #[error("Generic error.")]
198    BogusError = 42,
199    #[error("Operation cancelled by user.")]
200    CancelError = 43,
201    #[error("Object ID too low.")]
202    OMObjLowErr = 44,
203    #[error("Object ID too high.")]
204    OMObjHiErr = 45,
205    #[error("Object not in heap.")]
206    OMObjNotInHeapErr = 46,
207    #[error("Unknown heap.")]
208    OMOHeapNotKnownErr = 47,
209    #[error("Unknown object (invalid DefProc).")]
210    OMBadDPIdErr = 48,
211    #[error("Unknown object (DefProc not in table).")]
212    OMNoDPinTabErr = 49,
213    #[error("Message out of range.")]
214    OMMsgOutOfRangeErr = 50,
215    #[error("Null method.")]
216    OMMethodNullErr = 51,
217    #[error("Unknown message.")]
218    OMUnknownMsgErr = 52,
219    #[error("Manager call not supported.")]
220    MgNotSupported = 53,
221    #[error("The network address is ill-formed.")]
222    NcBadAddressErr = 54,
223    #[error("The network operation is in progress.")]
224    NcInProgress = 55,
225    #[error("The network operation exceeded the user-specified or system time limit.")]
226    NcTimeOutErr = 56,
227    #[error("The network connection is busy.")]
228    NcBusyErr = 57,
229    #[error("The network function is not supported by the system.")]
230    NcNotSupportedErr = 58,
231    #[error("The network is down, unreachable, or has been reset.")]
232    NcNetErr = 59,
233    #[error(
234                "The specified port or network address is currently in use. Select an available port or network address."
235            )]
236    NcAddrInUseErr = 60,
237    #[error("The system could not allocate the necessary memory.")]
238    NcSysOutOfMem = 61,
239    #[error("The system caused the network connection to be aborted.")]
240    NcSysConnAbortedErr = 62,
241    #[error("The network connection was refused by the server.")]
242    NcConnRefusedErr = 63,
243    #[error("The network connection is not yet established.")]
244    NcNotConnectedErr = 64,
245    #[error("The network connection is already established.")]
246    NcAlreadyConnectedErr = 65,
247    #[error("The network connection was closed by the peer.")]
248    NcConnClosedErr = 66,
249    #[error("Interapplication Manager initialization error.")]
250    AmInitErr = 67,
251    #[error("Bad occurrence.")]
252    OccBadOccurrenceErr = 68,
253    #[error("Handler does not know what occurrence to wait for.")]
254    OccWaitOnUnBoundHdlrErr = 69,
255    #[error("Occurrence queue overflow.")]
256    OccFunnyQOverFlowErr = 70,
257    #[error("File datalog type conflict.")]
258    FDataLogTypeConflict = 71,
259    #[error("Semaphore not signaled.")]
260    EcLVSBCannotBeCalledFromThread = 72,
261    #[error("Interapplication Manager unrecognized type error.")]
262    AmUnrecognizedType = 73,
263    #[error("Memory or data structure corrupt.")]
264    MCorruptErr = 74,
265    #[error("Failed to make temporary DLL.")]
266    EcLVSBErrorMakingTempDLL = 75,
267    #[error("Old CIN version.")]
268    EcLVSBOldCIN = 76,
269    #[error("Format specifier type mismatch.")]
270    FmtTypeMismatch = 81,
271    #[error("Unknown format specifier.")]
272    FmtUnknownConversion = 82,
273    #[error("Too few format specifiers.")]
274    FmtTooFew = 83,
275    #[error("Too many format specifiers.")]
276    FmtTooMany = 84,
277    #[error("Scan failed. The input string does not contain data in the expected format.")]
278    FmtScanError = 85,
279    #[error("Error converting to variant.")]
280    LvOLEConvertErr = 87,
281    #[error("Run-time menu error.")]
282    RtMenuErr = 88,
283    #[error("Another user tampered with the VI password.")]
284    PwdTampered = 89,
285    #[error("Variant attribute not found.")]
286    LvVariantAttrNotFound = 90,
287    #[error(
288                "The data type of the variant is not compatible with the data type wired to the type input."
289            )]
290    LvVariantTypeMismatch = 91,
291    #[error("The ActiveX event data was not available on the queue.")]
292    AxEventDataNotAvailable = 92,
293    #[error("ActiveX event information was not available.")]
294    AxEventStoreNotPresent = 93,
295    #[error("The occurrence associated with the ActiveX event was not found.")]
296    AxOccurrenceNotFound = 94,
297    #[error("The ActiveX event queue could not be created.")]
298    AxEventQueueNotCreated = 95,
299    #[error("ActiveX event information was not available in the type library.")]
300    AxEventInfoNotAvailable = 96,
301    #[error("A null or previously deleted refnum was passed in as an input.")]
302    OleNullRefnumPassed = 97,
303    #[error("IVI invalid downcast.")]
304    IviInvalidDowncast = 102,
305    #[error("No IVI class session opened.")]
306    IviInvalidClassSesn = 103,
307    #[error("Singlecast connections cannot send to multicast addresses.")]
308    NcSockNotMulticast = 108,
309    #[error("Multicast connections cannot send to singlecast addresses.")]
310    NcSockNotSinglecast = 109,
311    #[error("Specified IP address is not in multicast address range.")]
312    NcBadMulticastAddr = 110,
313    #[error("Cannot write to read-only multicast connection.")]
314    NcMcastSockReadOnly = 111,
315    #[error("Cannot read from write-only multicast connection.")]
316    NcMcastSockWriteOnly = 112,
317    #[error(
318                "A message sent on a datagram socket was larger than the internal message buffer or some other network limit, or the buffer used to receive a datagram was smaller than the datagram itself."
319            )]
320    NcDatagramMsgSzErr = 113,
321    #[error(
322                "Unflatten or byte stream read operation failed due to corrupt, unexpected, or truncated data."
323            )]
324    DataCorruptErr = 116,
325    #[error(
326                "Directory path supplied where a file path is required. A file path with the filename is required, but the supplied path is a path to a directory."
327            )]
328    RequireFullPathErr = 117,
329    #[error("The supplied folder path does not exist.")]
330    FolderNotExistErr = 118,
331    #[error("Illegal combination of Bluetooth discoverable and non-connectable modes.")]
332    NcBtInvalidModeErr = 119,
333    #[error("Error setting Bluetooth mode.")]
334    NcBtSetModeErr = 120,
335    #[error("Invalid GUID string.")]
336    MgBtInvalidGUIDStrErr = 121,
337    #[error(
338                "The resource you are attempting to open was created in a more recent version of LabVIEW and is incompatible with this version."
339            )]
340    RVersInFuture = 122,
341}
342
343impl TryFrom<LVStatusCode> for MgError {
344    type Error = LVInteropError;
345    fn try_from(status: LVStatusCode) -> ::core::result::Result<Self, Self::Error> {
346        // SUCCESS is not a valid error!
347        if status == LVStatusCode::SUCCESS {
348            return Err(InternalError::InvalidMgErrorCode.into());
349        }
350        match MgError::try_from_primitive(status.into()) {
351            Ok(code) => Ok(code),
352            Err(_) => Err(InternalError::InvalidMgErrorCode.into()),
353        }
354    }
355}
356
357impl From<&MgError> for LVStatusCode {
358    fn from(errcode: &MgError) -> LVStatusCode {
359        let erri32: i32 = *errcode as i32;
360        erri32.into()
361    }
362}
363
364impl From<MgError> for LVStatusCode {
365    fn from(errcode: MgError) -> LVStatusCode {
366        (&errcode).into()
367    }
368}
369
370/// # Examples
371///
372/// ```
373/// use labview_interop::errors::{MgError, LVInteropError};
374/// use labview_interop::types::LVStatusCode;
375/// use std::convert::TryFrom;
376///
377/// let status = LVStatusCode::from(2);
378/// let result: Result<MgError, LVInteropError> = MgError::try_from(status);
379/// assert!(result.is_ok());
380/// assert_eq!(result.unwrap(), MgError::MFullErr);
381///
382/// let status = LVStatusCode::from(0);
383/// let result: Result<MgError, LVInteropError> = MgError::try_from(status);
384/// assert!(result.is_err());
385///
386/// let error = MgError::BogusError;
387/// let error_code: i32 = error.into();
388/// assert_eq!(error_code, 42);
389/// ```
390///
391/// LVInteropError is our internal Error type
392/// in order to be able to easily convert it to LV ErrorClusters all Errors should possess an
393/// Error Code
394///
395/// Our choice of a custom ranges in Labview is (see comment above on valid ranges)
396/// 542,000 to 542,999
397#[derive(Error, Debug, Clone, PartialEq)]
398#[repr(i32)]
399pub enum InternalError {
400    #[error("LabVIEW Interop General Error. Probably because of a missing implementation.")]
401    Misc = 542_000,
402    #[error("LabVIEW API unavailable. Probably because it isn't being run in LabVIEW. Source Error: {0}")]
403    NoLabviewApi(String) = 542_001,
404    #[error("Invalid handle when valid handle is required")]
405    InvalidHandle = 542_002,
406    #[error("LabVIEW arrays can only have dimensions of i32 range.")]
407    ArrayDimensionsOutOfRange = 542_003,
408    #[error(
409        "Array dimensions don't match. You may require the link feature to enable auto-resizing."
410    )]
411    ArrayDimensionMismatch = 542_004,
412    #[error("Creating of handle in LabVIEW memory manager failed. Perhaps you are out of memory?")]
413    HandleCreationFailed = 542_005,
414    #[error("Invalid numeric status code for conversion into enumerated error code")]
415    InvalidMgErrorCode = 542_006,
416}
417
418impl From<&InternalError> for LVStatusCode {
419    fn from(err: &InternalError) -> LVStatusCode {
420        let err_i32: i32 = match err {
421            InternalError::Misc => 542_000,
422            InternalError::NoLabviewApi(_) => 542_001,
423            InternalError::InvalidHandle => 542_002,
424            InternalError::ArrayDimensionsOutOfRange => 542_003,
425            InternalError::ArrayDimensionMismatch => 542_004,
426            InternalError::HandleCreationFailed => 542_005,
427            InternalError::InvalidMgErrorCode => 542_006,
428        };
429        err_i32.into()
430    }
431}
432#[derive(Error, Debug, Clone, PartialEq)]
433pub enum LVInteropError {
434    #[error("Internal LabVIEW Manager Error: {0}")]
435    LabviewMgError(#[from] MgError),
436    #[error("Internal Error: {0}")]
437    InternalError(#[from] InternalError),
438    #[error("LabVIEW Error: {0}")]
439    LabviewError(LVStatusCode),
440}
441
442pub type Result<T> = std::result::Result<T, LVInteropError>;
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447
448    #[test]
449    fn test_error_lvstatuscode_from_mgerror() {
450        let err = MgError::BogusError;
451        let status: LVStatusCode = err.into();
452
453        assert_eq!(LVStatusCode::from(42), status)
454    }
455
456    #[test]
457    fn test_error_lvinteroperror_from_lvstatuscode() {
458        let status = LVStatusCode::from(42);
459        let mg_err = MgError::try_from(status).unwrap();
460
461        let expected_code: LVStatusCode = 42.into();
462        assert_eq!(expected_code, mg_err.into());
463    }
464
465    #[test]
466    fn test_error_lvstatuscode_from_lvinteroperror() {
467        let err: LVInteropError = MgError::BogusError.into();
468        let status: LVStatusCode = LVStatusCode::from(42);
469        assert_eq!(status, err.into());
470
471        let err: LVInteropError =
472            InternalError::NoLabviewApi("Test Inner message".to_string()).into();
473        let status: LVStatusCode = LVStatusCode::from(542_001);
474
475        println!("{}", status);
476        assert_eq!(status, err.into());
477
478        //let err = LVStatusCode::from(42);
479        //assert
480
481        //let num: i32 = err.code().into();
482        //assert_eq!(num, 42);
483        //println!("{}", err);
484    }
485}