Skip to content

Commit

Permalink
Merge pull request #39 from WiresmithTech/feature/#35-error-wrapping
Browse files Browse the repository at this point in the history
Feature/#35 error wrapping
  • Loading branch information
JamesMc86 authored Sep 17, 2024
2 parents c67b1d1 + 1a68ce4 commit 098b239
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 21 deletions.
2 changes: 1 addition & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

* Added support for `LVStatusCode` and retrieving error descriptions from LabVIEW.
* Added api for accessing dimensions of arrays.
* Add API to error clusters to execute the rust code following the LabVIEW error semantics.
* Lazily load LabVIEW functions to allow time for LabVIEW to load.

#### Fixes

* Expose NumericArrayResize trait to users of the Library.
Expand Down
40 changes: 28 additions & 12 deletions labview-interop/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,32 +55,42 @@
//! This enum has custom errors for our internal use, we can hold MgErrors, and as last resort we can also hold
//! LVError
use thiserror::Error;

use crate::types::LVStatusCode;

use num_enum::{IntoPrimitive, TryFromPrimitive};
use thiserror::Error;

/// the conversion from LVInteropError back to LVStatusCode is important
/// to return the status in extern "C" functions back to LV
impl<T> From<Result<T>> for LVStatusCode {
fn from(value: Result<T>) -> Self {
match value {
Ok(_) => LVStatusCode::SUCCESS,
Err(err) => err.into(),
Err(err) => (&err).into(),
}
}
}
impl From<LVInteropError> for LVStatusCode {
fn from(value: LVInteropError) -> Self {
impl From<&LVInteropError> for LVStatusCode {
fn from(value: &LVInteropError) -> Self {
match value {
LVInteropError::LabviewMgError(e) => e.into(),
LVInteropError::InternalError(e) => e.into(), // TODO
LVInteropError::LabviewError(e) => e,
LVInteropError::InternalError(e) => e.into(),
LVInteropError::LabviewError(e) => *e,
}
}
}

impl From<LVInteropError> for LVStatusCode {
fn from(value: LVInteropError) -> Self {
(&value).into()
}
}

impl From<LVStatusCode> for LVInteropError {
fn from(status: LVStatusCode) -> Self {
LVInteropError::LabviewError(status)
}
}

/// MgError is the subset of LabVIEW errors that may occur when dealing with the memory manager
/// So in the context of Rust-LabVIEW-interop these are the kind of labview errors we may trigger within the library
///
Expand Down Expand Up @@ -344,10 +354,16 @@ impl TryFrom<LVStatusCode> for MgError {
}
}

impl From<&MgError> for LVStatusCode {
fn from(errcode: &MgError) -> LVStatusCode {
let erri32: i32 = *errcode as i32;
erri32.into()
}
}

impl From<MgError> for LVStatusCode {
fn from(errcode: MgError) -> LVStatusCode {
let erri32: i32 = errcode.into();
erri32.into()
(&errcode).into()
}
}

Expand Down Expand Up @@ -399,8 +415,8 @@ pub enum InternalError {
InvalidMgErrorCode = 542_006,
}

impl From<InternalError> for LVStatusCode {
fn from(err: InternalError) -> LVStatusCode {
impl From<&InternalError> for LVStatusCode {
fn from(err: &InternalError) -> LVStatusCode {
let err_i32: i32 = match err {
InternalError::Misc => 542_000,
InternalError::NoLabviewApi(_) => 542_001,
Expand Down
19 changes: 19 additions & 0 deletions labview-interop/src/types/boolean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
///
/// You can use `.into()` to convert between this and
/// rust [`bool`] types.
///
/// NOTE: This does not seem to work if the boolean is
/// used as a parameter in a call library function node.
/// In that case you should convert to numeric first.
#[repr(transparent)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct LVBool(u8);
Expand Down Expand Up @@ -65,4 +69,19 @@ mod tests {
let value: bool = LVBool(23).into();
assert_eq!(value, true)
}

#[test]
fn lv_bool_in_if_statement() {
if LV_TRUE.into() {
assert!(true);
} else {
assert!(false);
}

if LV_FALSE.into() {
assert!(false);
} else {
assert!(true);
}
}
}
112 changes: 107 additions & 5 deletions labview-interop/src/types/lv_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
//!
//! This is only available in 64 bit currently due to restrictions
//! on unaligned pointer access.
use std::borrow::Cow;

#[cfg(feature = "link")]
use crate::errors::Result;
use crate::errors::{LVInteropError, MgError};
Expand All @@ -12,6 +10,7 @@ use crate::memory::UPtr;
use crate::types::LStrHandle;
use crate::types::LVBool;
use crate::types::LVStatusCode;
use std::borrow::Cow;

labview_layout!(
/// The cluster format used by LabVIEW for transmitting errors.
Expand All @@ -22,12 +21,19 @@ labview_layout!(
}
);

impl<'a> ErrorCluster<'a> {
/// Does the error cluster contain an error.
pub fn is_err(&self) -> bool {
self.status.into()
}
}

/// The pointer as passed by LabVIEW when using "Handles By Value" for type.
///
/// Debugging shows only one level of indirection hence UPtr here.
///
/// It is recommended to manually call `ErrorClusterPtr::as_ref` or `ErrorClusterPtr::as_mut`
/// so that null pointeres can be detected.
/// so that null pointers can be detected.
///
/// Many string manipulation functions are only available with the `link` feature enabled so
/// it can manipulate LabVIEW Strings.
Expand All @@ -44,6 +50,92 @@ fn format_error_source(source: &str, description: &str) -> String {
}
}

#[cfg(feature = "link")]
impl ErrorClusterPtr<'_> {
/// Wrap the provided function in error handling to match LabVIEW semantics.
///
/// i.e. no execution on error in, convert return errors into error cluster.
///
/// ## Parameters
///
/// - `return_on_error` - The value to return if an error.
/// - `function` - The function to wrap. This is intended to be a closure for
/// easy use.
///
/// ## Example
///
/// ```rust
/// use labview_interop::types::ErrorClusterPtr;
/// use labview_interop::errors::LVInteropError;
/// use labview_interop::types::LStrHandle;
///
/// #[no_mangle]
/// pub extern "C" fn example_function(mut error_cluster: ErrorClusterPtr, mut string_input: LStrHandle) -> i32 {
/// error_cluster.wrap_function(42, || -> Result<i32, LVInteropError> {
/// // Do some work
/// string_input.set_str("Hello World")?;
/// Ok(42)
/// })
/// }
/// ```
pub fn wrap_function<R, E: ToLvError, F: FnOnce() -> std::result::Result<R, E>>(
&mut self,
return_on_error: R,
function: F,
) -> R {
if self.is_err() {
return return_on_error;
}
match function() {
Ok(value) => value,
Err(error) => {
let _ = error.write_error(self);
return_on_error
}
}
}

/// Wrap the provided function in error handling to match LabVIEW semantics.
///
/// i.e. no execution on error in, convert return errors into error cluster.
///
/// This version returns the LabVIEW status code of the error.
/// To return a different value, see [`ErrorClusterPtr::wrap_function`].
///
/// ## Parameters
///
/// - `function` - The function to wrap. This is intended to be a closure for
/// easy use.
///
/// ## Example
///
/// ```rust
/// use labview_interop::types::{ErrorClusterPtr, LVStatusCode};
/// use labview_interop::errors::LVInteropError;
/// use labview_interop::types::LStrHandle;
///
/// #[no_mangle]
/// pub extern "C" fn example_function(mut error_cluster: ErrorClusterPtr, mut string_input: LStrHandle) -> LVStatusCode {
/// error_cluster.wrap_return_status(|| -> Result<(), LVInteropError> {
/// // Do some work
/// string_input.set_str("Hello World")?;
/// Ok(())
///
/// })
/// }
/// ```
pub fn wrap_return_status<E: ToLvError, F: FnOnce() -> std::result::Result<(), E>>(
&mut self,
function: F,
) -> LVStatusCode {
if self.is_err() {
return self.code;
}
self.wrap_function((), function);
self.code
}
}

#[cfg(feature = "link")]
mod error_cluster_link_features {
use super::*;
Expand Down Expand Up @@ -89,7 +181,7 @@ mod error_cluster_link_features {
pub trait ToLvError {
/// The code for the error. Default is 42.
fn code(&self) -> LVStatusCode {
MgError::BogusError.into() // code 42, Generic Error
(&MgError::BogusError).into() // code 42, Generic Error
}

/// True if is error. Default is true.
Expand All @@ -112,7 +204,7 @@ pub trait ToLvError {
///
/// This requires the `link` feature to enable string manipulation.
#[cfg(feature = "link")]
fn write_error(&self, error_cluster: ErrorClusterPtr) -> Result<()> {
fn write_error(&self, error_cluster: &mut ErrorClusterPtr) -> Result<()> {
let cluster = unsafe { error_cluster.as_ref_mut()? };
let code = self.code();
let source = self.source();
Expand All @@ -128,6 +220,16 @@ pub trait ToLvError {
}

impl ToLvError for LVInteropError {
fn code(&self) -> LVStatusCode {
self.into()
}
fn source(&self) -> Cow<'_, str> {
std::error::Error::source(self)
.map(|s| s.to_string())
.unwrap_or_default()
.into()
}

fn description(&self) -> Cow<'_, str> {
self.to_string().into()
}
Expand Down
22 changes: 20 additions & 2 deletions labview-test-library/src/error_clusters.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use labview_interop::errors::LVInteropError;
/// A simple type for testing the error integration.
///
use labview_interop::types::{ErrorClusterPtr, ToLvError};
Expand All @@ -17,9 +18,26 @@ impl ToLvError for ErrorText {

#[cfg(target_pointer_width = "64")]
#[no_mangle]
pub extern "C" fn set_error_cluster(error_cluster: ErrorClusterPtr) -> LVStatusCode {
pub extern "C" fn set_error_cluster(mut error_cluster: ErrorClusterPtr) -> LVStatusCode {
let error = ErrorText("This is a test");
error.write_error(error_cluster).into()
error.write_error(&mut error_cluster).into()
}

/// Check scenarios for the error cluster function wrapping.
#[cfg(target_pointer_width = "64")]
#[no_mangle]
pub extern "C" fn wrap_function(
mut error_cluster: ErrorClusterPtr,
mut text: LStrHandle,
inner_error: i16,
) -> i32 {
error_cluster.wrap_function(42, || -> Result<i32, LVInteropError> {
text.set_str("Hello World")?;
if inner_error != 0 {
return Err(LVInteropError::from(LVStatusCode::from(1)));
}
Ok(0)
})
}

#[no_mangle]
Expand Down
27 changes: 27 additions & 0 deletions labview-test-project/Error Tests/Error Tests.lvclass
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,31 @@
<Property Name="NI.ClassItem.Priority" Type="Int">1</Property>
<Property Name="NI.ClassItem.State" Type="Int">1342710288</Property>
</Item>
<Item Name="test Error Wrapping - No Execute On Error In.vi" Type="VI" URL="../test Error Wrapping - No Execute On Error In.vi">
<Property Name="NI.ClassItem.ConnectorPane" Type="Bin">)!#!!!!!!!)!"1!&amp;!!!-!%!!!@````]!!!!"!!%!!!%A!!!!#1!-1#%'=X2B&gt;(6T!!!,1!-!"'.P:'5!!""!-0````]'=W^V=G.F!!!71&amp;!!!Q!!!!%!!AFF=H*P=C"P&gt;81!"!!!!#R!=!!?!!!6%U6S=G^S)&amp;2F=X2T,GRW9WRB=X-!$&amp;2F=X2$98.F)'^V&gt;!!!)%"1!!-!!!!"!!)4:8*S&lt;X)A;7YA+'ZP)'6S=G^S+1!K1(!!(A!!&amp;2.&amp;=H*P=C"5:8.U=SZM&gt;G.M98.T!!N5:8.U1W&amp;T:3"J&lt;A"B!0!!$!!$!!1!"!!&amp;!!1!"!!%!!1!"A!%!!1!"Q-!!(A!!!U)!!!!!!!!!!!!!!U,!!!!!!!!!!!!!!!!!!!!!!!!#A!!!!!!!!!!!!!!%A!!$1!!!!Q!!!!!!!!!!!!!!1!)!!!!!!</Property>
<Property Name="NI.ClassItem.ExecutionSystem" Type="Int">-1</Property>
<Property Name="NI.ClassItem.Flags" Type="Int">16777216</Property>
<Property Name="NI.ClassItem.IsStaticMethod" Type="Bool">true</Property>
<Property Name="NI.ClassItem.MethodScope" Type="UInt">1</Property>
<Property Name="NI.ClassItem.Priority" Type="Int">1</Property>
<Property Name="NI.ClassItem.State" Type="Int">1342710288</Property>
</Item>
<Item Name="test Error Wrapping - Execute if No Error In.vi" Type="VI" URL="../test Error Wrapping - Execute if No Error In.vi">
<Property Name="NI.ClassItem.ConnectorPane" Type="Bin">)!#!!!!!!!)!"1!&amp;!!!-!%!!!@````]!!!!"!!%!!!%A!!!!#1!-1#%'=X2B&gt;(6T!!!,1!-!"'.P:'5!!""!-0````]'=W^V=G.F!!!71&amp;!!!Q!!!!%!!AFF=H*P=C"P&gt;81!"!!!!#R!=!!?!!!6%U6S=G^S)&amp;2F=X2T,GRW9WRB=X-!$&amp;2F=X2$98.F)'^V&gt;!!!)%"1!!-!!!!"!!)4:8*S&lt;X)A;7YA+'ZP)'6S=G^S+1!K1(!!(A!!&amp;2.&amp;=H*P=C"5:8.U=SZM&gt;G.M98.T!!N5:8.U1W&amp;T:3"J&lt;A"B!0!!$!!$!!1!"!!&amp;!!1!"!!%!!1!"A!%!!1!"Q-!!(A!!!U)!!!!!!!!!!!!!!U,!!!!!!!!!!!!!!!!!!!!!!!!#A!!!!!!!!!!!!!!%A!!$1!!!!Q!!!!!!!!!!!!!!1!)!!!!!!</Property>
<Property Name="NI.ClassItem.ExecutionSystem" Type="Int">-1</Property>
<Property Name="NI.ClassItem.Flags" Type="Int">32</Property>
<Property Name="NI.ClassItem.IsStaticMethod" Type="Bool">true</Property>
<Property Name="NI.ClassItem.MethodScope" Type="UInt">1</Property>
<Property Name="NI.ClassItem.Priority" Type="Int">1</Property>
<Property Name="NI.ClassItem.State" Type="Int">1074278928</Property>
</Item>
<Item Name="test Error Wrapping - Sets Error For Inner Error.vi" Type="VI" URL="../test Error Wrapping - Sets Error For Inner Error.vi">
<Property Name="NI.ClassItem.ConnectorPane" Type="Bin">)!#!!!!!!!)!"1!&amp;!!!-!%!!!@````]!!!!"!!%!!!%A!!!!#1!-1#%'=X2B&gt;(6T!!!,1!-!"'.P:'5!!""!-0````]'=W^V=G.F!!!71&amp;!!!Q!!!!%!!AFF=H*P=C"P&gt;81!"!!!!#R!=!!?!!!6%U6S=G^S)&amp;2F=X2T,GRW9WRB=X-!$&amp;2F=X2$98.F)'^V&gt;!!!)%"1!!-!!!!"!!)4:8*S&lt;X)A;7YA+'ZP)'6S=G^S+1!K1(!!(A!!&amp;2.&amp;=H*P=C"5:8.U=SZM&gt;G.M98.T!!N5:8.U1W&amp;T:3"J&lt;A"B!0!!$!!$!!1!"!!&amp;!!1!"!!%!!1!"A!%!!1!"Q-!!(A!!!U)!!!!!!!!!!!!!!U,!!!!!!!!!!!!!!!!!!!!!!!!#A!!!!!!!!!!!!!!%A!!$1!!!!Q!!!!!!!!!!!!!!1!)!!!!!!</Property>
<Property Name="NI.ClassItem.ExecutionSystem" Type="Int">-1</Property>
<Property Name="NI.ClassItem.Flags" Type="Int">16777216</Property>
<Property Name="NI.ClassItem.IsStaticMethod" Type="Bool">true</Property>
<Property Name="NI.ClassItem.MethodScope" Type="UInt">1</Property>
<Property Name="NI.ClassItem.Priority" Type="Int">1</Property>
<Property Name="NI.ClassItem.State" Type="Int">1342710288</Property>
</Item>
</LVClass>
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion labview-test-project/rust-interop-test.lvlps
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[ProjectWindow_Data]
ProjectExplorer.ClassicPosition[String] = "37,769,915,1535"
ProjectExplorer.ClassicPosition[String] = "33,769,911,1535"

0 comments on commit 098b239

Please sign in to comment.