Skip to content

Feature/#35 error wrapping #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"