jaylink 0.3.0

Library to communicate with J-Link USB devices
Documentation
use std::fmt;

type BoxedError = Box<dyn std::error::Error + Send + Sync>;

#[allow(unused_imports)] // for intra-doc links
use crate::{scan_usb, Capabilities, JayLink};

/// List of specific errors that may occur when using this library.
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ErrorKind {
    /// A USB transport error occurred.
    ///
    /// This variant is used for all errors reported by the operating system when performing a USB
    /// operation. It may indicate that the USB device was unplugged, that another application or an
    /// operating system driver is currently using it, or that the current user does not have
    /// permission to access it.
    Usb,

    /// No (matching) J-Link device was found.
    ///
    /// This error occurs when calling [`JayLink::open_by_serial`] while no J-Link device is connected
    /// (or no device matching the serial number is connected).
    DeviceNotFound,

    /// Automatic device connection failed because multiple devices were found.
    ///
    /// This error occurs when calling [`JayLink::open_by_serial`] without a serial number while
    /// multiple J-Link devices are connected. This library will refuse to "guess" a device and
    /// requires specifying a serial number in this case. The [`scan_usb`] function can also be used
    /// to find a specific device to connect to.
    MultipleDevicesFound,

    /// A operation was attempted that is not supported by the probe.
    ///
    /// Some operations are not supported by all firmware/hardware versions, and are instead
    /// advertised as optional *capability* bits. This error occurs when the capability bit for an
    /// operation isn't set when that operation is attempted.
    ///
    /// Capabilities can be read by calling [`JayLink::capabilities`], which returns a
    /// [`Capabilities`] bitflags struct.
    MissingCapability,

    /// The device does not support the selected target interface.
    InterfaceNotSupported,

    /// An unspecified error occurred.
    Other,
}

pub(crate) trait Cause {
    const KIND: ErrorKind;
}

/// The error type used by this library.
///
/// Errors can be introspected by the user by calling [`Error::kind`] and inspecting the returned
/// [`ErrorKind`].
#[derive(Debug)]
pub struct Error {
    kind: ErrorKind,
    inner: BoxedError,
    while_: Option<&'static str>,
}

impl Error {
    pub(crate) fn new(kind: ErrorKind, inner: impl Into<BoxedError>) -> Self {
        Self {
            kind,
            inner: inner.into(),
            while_: None,
        }
    }

    pub(crate) fn with_while(
        kind: ErrorKind,
        inner: impl Into<BoxedError>,
        while_: &'static str,
    ) -> Self {
        Self {
            kind,
            inner: inner.into(),
            while_: Some(while_),
        }
    }

    fn fmt_while(&self) -> String {
        if let Some(while_) = self.while_ {
            format!(" while {}", while_)
        } else {
            String::new()
        }
    }

    /// Returns the [`ErrorKind`] describing this error.
    pub fn kind(&self) -> ErrorKind {
        self.kind
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Prefix foreign errors with further explanation where they're coming from
        match self.kind {
            ErrorKind::Usb => write!(f, "USB error{}: {}", self.fmt_while(), self.inner),
            _ => {
                if let Some(while_) = self.while_ {
                    write!(f, "error{}: {}", while_, self.inner)
                } else {
                    self.inner.fmt(f)
                }
            }
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.inner.source()
    }
}

pub(crate) trait ResultExt<T, E> {
    fn jaylink_err(self) -> Result<T, Error>
    where
        E: Cause + Into<BoxedError>;

    fn jaylink_err_while(self, while_: &'static str) -> Result<T, Error>
    where
        E: Cause + Into<BoxedError>;
}

impl<T, E> ResultExt<T, E> for Result<T, E> {
    fn jaylink_err(self) -> Result<T, Error>
    where
        E: Cause + Into<BoxedError>,
    {
        self.map_err(|e| Error::new(E::KIND, e))
    }

    fn jaylink_err_while(self, while_: &'static str) -> Result<T, Error>
    where
        E: Cause + Into<BoxedError>,
    {
        self.map_err(|e| Error::with_while(E::KIND, e, while_))
    }
}

macro_rules! error_mapping {
    (
        $(
            $errty:ty => $kind:ident,
        )+
    ) => {
        $(
            impl Cause for $errty {
                const KIND: ErrorKind = ErrorKind::$kind;
            }
        )+
    };
}

error_mapping! {
    rusb::Error => Usb,
    String => Other,
}