From 81e34b33e454f058ae1dee0293cc4d66140486a9 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Fri, 19 Jan 2024 17:36:33 -0800 Subject: [PATCH] Clean up the error module Formerly errors. Can now also convert from Record. --- CONTRIBUTING.md | 4 +- Cargo.toml | 2 + README.md | 3 +- src/error/experimental.rs | 189 ++++++++++++++++++++++++++++++++ src/{errors.rs => error/mod.rs} | 145 ++---------------------- src/lib.rs | 11 +- 6 files changed, 210 insertions(+), 144 deletions(-) create mode 100644 src/error/experimental.rs rename src/{errors.rs => error/mod.rs} (52%) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d64163..b649997 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,7 +39,7 @@ The following software is recommended: To build and test, simply run: ```powershell -cargo test --all --features nightly +cargo test --features nightly ``` By default, this will build x64 custom action DLLs from under _examples_. @@ -61,7 +61,7 @@ If you have the x86 libraries for the Windows SDK installed, you can also build ```powershell rustup target install i686-pc-windows-msvc -cargo test --all --target i686-pc-windows-msvc +cargo test --target i686-pc-windows-msvc msbuild -t:rebuild examples/product.wixproj -p:Platform=x86 ``` diff --git a/Cargo.toml b/Cargo.toml index af852d4..95beaa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,10 @@ default-target = "x86_64-pc-windows-msvc" name = "deferred" crate-type = ["cdylib"] path = "examples/deferred/lib.rs" +required-features = ["nightly"] [[example]] name = "skip" crate-type = ["cdylib"] path = "examples/skip/lib.rs" +required-features = ["nightly"] diff --git a/README.md b/README.md index 938338b..23cb151 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ pub extern "C" fn MyCustomAction(session: Session) -> u32 { ### Using nightly feature -If you enable the `nightly` feature, you can use the question mark operator (`?`) to propagate errors: +If you enable the `nightly` feature and use the nightly toolchain, you can use the question mark operator (`?`) to +propagate errors: ```rust use msica::*; diff --git a/src/error/experimental.rs b/src/error/experimental.rs new file mode 100644 index 0000000..8d6c6c2 --- /dev/null +++ b/src/error/experimental.rs @@ -0,0 +1,189 @@ +// Copyright 2022 Heath Stewart. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +#![cfg(feature = "nightly")] +use super::{Error, ErrorKind}; +use crate::ffi; +use std::convert::Infallible; +use std::fmt::Display; +use std::num::NonZeroU32; +use std::ops::{ControlFlow, FromResidual, Try}; + +/// A result to return from a custom action. +/// +/// This allows you to use the `?` operator to map any `Result` to [`CustomActionResult::Fail`]. +/// +/// # Example +/// +/// ```no_run +/// use std::ffi::OsString; +/// use msica::{Session, CustomActionResult}; +/// +/// #[no_mangle] +/// pub extern "C" fn MyCustomAction(session: Session) -> CustomActionResult { +/// let productName = session.property("ProductName")?; +/// +/// // Do something with `productName`. +/// +/// CustomActionResult::Succeed +/// } +/// ``` +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[repr(u32)] +pub enum CustomActionResult { + /// Completed actions successfully. + Succeed = ffi::ERROR_SUCCESS, + + /// Skip remaining actions. Not an error. + Skip = ffi::ERROR_NO_MORE_ITEMS, + + /// User terminated prematurely. + Cancel = ffi::ERROR_INSTALL_USEREXIT, + + /// Unrecoverable error occurred. + Fail = ffi::ERROR_INSTALL_FAILURE, + + /// Action not executed. + NotExecuted = ffi::ERROR_FUNCTION_NOT_CALLED, +} + +impl Display for CustomActionResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let error = match &self { + Self::Succeed => "succeeded", + Self::Skip => "skip remaining actions", + Self::Cancel => "user canceled", + Self::Fail => "failed", + Self::NotExecuted => "not executed", + }; + + write!(f, "{}", error) + } +} + +impl From for CustomActionResult { + fn from(code: u32) -> Self { + match code { + ffi::ERROR_SUCCESS => CustomActionResult::Succeed, + ffi::ERROR_NO_MORE_ITEMS => CustomActionResult::Skip, + ffi::ERROR_INSTALL_USEREXIT => CustomActionResult::Cancel, + ffi::ERROR_FUNCTION_NOT_CALLED => CustomActionResult::NotExecuted, + _ => CustomActionResult::Fail, + } + } +} + +#[allow(clippy::from_over_into)] +impl Into for CustomActionResult { + fn into(self) -> u32 { + self as u32 + } +} + +/// This type is an implementation detail and not intended for direct use. +#[doc(hidden)] +pub struct CustomActionResultCode(NonZeroU32); + +impl From for CustomActionResultCode { + fn from(value: CustomActionResult) -> Self { + CustomActionResultCode(NonZeroU32::new(value.into()).unwrap()) + } +} + +impl Try for CustomActionResult { + type Output = u32; + type Residual = CustomActionResultCode; + + fn branch(self) -> ControlFlow { + match self { + Self::Succeed => ControlFlow::Continue(ffi::ERROR_SUCCESS), + _ => ControlFlow::Break(self.into()), + } + } + + fn from_output(_: Self::Output) -> Self { + CustomActionResult::Succeed + } +} + +impl FromResidual for CustomActionResult { + fn from_residual(residual: CustomActionResultCode) -> Self { + match residual.0.into() { + ffi::ERROR_NO_MORE_ITEMS => CustomActionResult::Skip, + ffi::ERROR_INSTALL_USEREXIT => CustomActionResult::Cancel, + ffi::ERROR_INSTALL_FAILURE => CustomActionResult::Fail, + ffi::ERROR_FUNCTION_NOT_CALLED => CustomActionResult::NotExecuted, + code => panic!("unexpected error code {}", code), + } + } +} + +impl FromResidual> for CustomActionResult { + fn from_residual(residual: Result) -> Self { + let error = residual.unwrap_err(); + match error.kind() { + ErrorKind::ErrorCode(code) => CustomActionResult::from(code.get()), + _ => CustomActionResult::Fail, + } + } +} + +impl FromResidual> for CustomActionResult { + default fn from_residual(_: std::result::Result) -> Self { + CustomActionResult::Fail + } +} + +#[cfg(test)] +mod tests { + use crate::Record; + + use super::*; + + #[test] + fn from_u32() { + assert_eq!(CustomActionResult::Succeed, CustomActionResult::from(0u32)); + assert_eq!(CustomActionResult::Skip, CustomActionResult::from(259u32)); + assert_eq!( + CustomActionResult::Cancel, + CustomActionResult::from(1602u32) + ); + assert_eq!( + CustomActionResult::NotExecuted, + CustomActionResult::from(1626u32) + ); + assert_eq!(CustomActionResult::Fail, CustomActionResult::from(1603u32)); + assert_eq!(CustomActionResult::Fail, CustomActionResult::from(1u32)); + } + + #[test] + fn into_u32() { + assert_eq!(0u32, Into::::into(CustomActionResult::Succeed)); + assert_eq!(259u32, Into::::into(CustomActionResult::Skip)); + assert_eq!(1602u32, Into::::into(CustomActionResult::Cancel)); + assert_eq!(1603u32, Into::::into(CustomActionResult::Fail)); + assert_eq!(1626u32, Into::::into(CustomActionResult::NotExecuted)); + } + + #[test] + fn from_residual_custom_action_result() { + let f = || -> CustomActionResult { CustomActionResult::Skip }; + assert_eq!(259u32, f().into()); + } + + #[test] + fn from_residual_error() { + let f = || -> CustomActionResult { Err(Error::from_error_code(1602u32))? }; + assert_eq!(1602u32, f().into()); + + let r = Record::with_fields(Some("error"), vec![]).expect("failed to create record"); + let f = || -> CustomActionResult { Err(Error::from_error_record(r))? }; + assert_eq!(1603u32, f().into()); + } + + #[test] + fn from_residual_std_error() { + let f = || -> CustomActionResult { Err(std::io::Error::from_raw_os_error(5))? }; + assert_eq!(1603u32, f().into()); + } +} diff --git a/src/errors.rs b/src/error/mod.rs similarity index 52% rename from src/errors.rs rename to src/error/mod.rs index baceae5..b90a6b1 100644 --- a/src/errors.rs +++ b/src/error/mod.rs @@ -5,6 +5,8 @@ use crate::Record; use std::fmt::Display; use std::num::{NonZeroU32, TryFromIntError}; +pub mod experimental; + /// Results returned by this crate. pub type Result = std::result::Result; @@ -118,6 +120,12 @@ impl From for Error { } } +impl From for Error { + fn from(record: Record) -> Self { + Error::from_error_record(record) + } +} + #[derive(Debug)] enum Context { Simple(ErrorKind), @@ -131,141 +139,6 @@ struct Custom { error: Box, } -#[cfg(feature = "nightly")] -pub mod experimental { - use super::{Error, ErrorKind}; - use crate::ffi; - use std::convert::Infallible; - use std::fmt::Display; - use std::num::NonZeroU32; - use std::ops::{ControlFlow, FromResidual, Try}; - - /// A result to return from a custom action. - /// - /// This allows you to use the `?` operator to map any `Result` to [`CustomActionResult::Fail`]. - /// - /// # Example - /// - /// ```no_run - /// use std::ffi::OsString; - /// use msica::{Session, CustomActionResult}; - /// - /// #[no_mangle] - /// pub extern "C" fn MyCustomAction(session: Session) -> CustomActionResult { - /// let productName = session.property("ProductName")?; - /// - /// // Do something with `productName`. - /// - /// CustomActionResult::Succeed - /// } - /// ``` - #[derive(Debug, Copy, Clone, Eq, PartialEq)] - #[repr(u32)] - pub enum CustomActionResult { - /// Completed actions successfully. - Succeed = ffi::ERROR_SUCCESS, - - /// Skip remaining actions. Not an error. - Skip = ffi::ERROR_NO_MORE_ITEMS, - - /// User terminated prematurely. - Cancel = ffi::ERROR_INSTALL_USEREXIT, - - /// Unrecoverable error occurred. - Fail = ffi::ERROR_INSTALL_FAILURE, - - /// Action not executed. - NotExecuted = ffi::ERROR_FUNCTION_NOT_CALLED, - } - - impl Display for CustomActionResult { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let error = match &self { - Self::Succeed => "succeeded", - Self::Skip => "skip remaining actions", - Self::Cancel => "user canceled", - Self::Fail => "failed", - Self::NotExecuted => "not executed", - }; - - write!(f, "{}", error) - } - } - - impl From for CustomActionResult { - fn from(code: u32) -> Self { - match code { - ffi::ERROR_SUCCESS => CustomActionResult::Succeed, - ffi::ERROR_NO_MORE_ITEMS => CustomActionResult::Skip, - ffi::ERROR_INSTALL_USEREXIT => CustomActionResult::Cancel, - ffi::ERROR_FUNCTION_NOT_CALLED => CustomActionResult::NotExecuted, - _ => CustomActionResult::Fail, - } - } - } - - #[allow(clippy::from_over_into)] - impl Into for CustomActionResult { - fn into(self) -> u32 { - self as u32 - } - } - - /// This type is an implementation detail and not intended for direct use. - #[doc(hidden)] - pub struct CustomActionResultCode(NonZeroU32); - - impl From for CustomActionResultCode { - fn from(value: CustomActionResult) -> Self { - CustomActionResultCode(NonZeroU32::new(value.into()).unwrap()) - } - } - - impl Try for CustomActionResult { - type Output = u32; - type Residual = CustomActionResultCode; - - fn branch(self) -> ControlFlow { - match self { - Self::Succeed => ControlFlow::Continue(ffi::ERROR_SUCCESS), - _ => ControlFlow::Break(self.into()), - } - } - - fn from_output(_: Self::Output) -> Self { - CustomActionResult::Succeed - } - } - - impl FromResidual for CustomActionResult { - fn from_residual(residual: CustomActionResultCode) -> Self { - match residual.0.into() { - ffi::ERROR_NO_MORE_ITEMS => CustomActionResult::Skip, - ffi::ERROR_INSTALL_USEREXIT => CustomActionResult::Cancel, - ffi::ERROR_INSTALL_FAILURE => CustomActionResult::Fail, - ffi::ERROR_FUNCTION_NOT_CALLED => CustomActionResult::NotExecuted, - code => panic!("unexpected error code {}", code), - } - } - } - - impl FromResidual> for CustomActionResult { - fn from_residual(residual: Result) -> Self { - let error = residual.unwrap_err(); - match error.kind() { - ErrorKind::ErrorCode(code) => CustomActionResult::from(code.get()), - _ => CustomActionResult::Fail, - } - } - } - - impl FromResidual> for CustomActionResult { - default fn from_residual(_: std::result::Result) -> Self { - CustomActionResult::Fail - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -289,7 +162,7 @@ mod tests { vec![Field::StringData("text".to_owned())], ) .expect("failed to create record"); - let error = Error::from_error_record(record); + let error: Error = record.into(); assert_eq!(&ErrorKind::ErrorRecord, error.kind()); assert_eq!("error text", error.to_string()); } diff --git a/src/lib.rs b/src/lib.rs index bb444aa..de51323 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,7 @@ compile_error!("supported on windows only"); // for inspiration for the shape of this API. mod database; -mod errors; +mod error; mod ffi; mod record; mod session; @@ -21,8 +21,8 @@ mod view; pub use database::Database; #[cfg(feature = "nightly")] -pub use errors::experimental::CustomActionResult; -pub use errors::{Error, ErrorKind, Result}; +pub use error::experimental::CustomActionResult; +pub use error::{Error, ErrorKind, Result}; pub use record::{Field, Record}; pub use session::{MessageType, RunMode, Session}; pub use view::{ModifyMode, View}; @@ -32,8 +32,9 @@ pub use view::{ModifyMode, View}; /// # Example /// /// ``` -/// if let Some(error) = msica::last_error_record() { -/// println!("last error: {}", error.format_text()?); +/// if let Some(record) = msica::last_error_record() { +/// println!("last error: {}", record.format_text()?); +/// return Err(record.into()); /// } /// # Ok::<(), msica::Error>(()) /// ```