lemon_bugsnag_rs 0.1.2

A library for interacting with the BugSnag error-reporting and sessions APIs.
Documentation
use serde::Serialize;

#[cfg(feature = "optional-builders")]
use crate::common::builder::error::BuilderError;

use super::{enums::RuntimeType, StackTrace};

/// Struct corresponding to the `events -> exceptions` key in the BugSnag
/// error-reporting API payload.
///
/// ```
/// use lemon_bugsnag_rs::error::payload::events::Exception;
///
/// let exception = Exception::default();
/// ```
#[derive(Clone, Debug, Default, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Exception {
    /// The class of an error used to group errors together in Bugsnag
    pub error_class: String,
    /// The message describing the error
    pub message: Option<String>,
    #[serde(rename = "stacktrace")]
    /// A list of stacktraces that lead to the error
    pub stack_trace: Vec<StackTrace>,
    /// The runtime type where the error originated from
    #[serde(rename(serialize = "type"))]
    pub runtime_type: Option<RuntimeType>,
}

#[cfg_attr(docsrs, doc(cfg(feature = "optional-builders")))]
#[cfg(feature = "optional-builders")]
impl Exception {
    /// Retrieve an [ExceptionBuilder].
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::Exception;
    ///
    /// let mut eb = Exception::builder();
    /// ```
    pub fn builder() -> ExceptionBuilder {
        ExceptionBuilder::default()
    }
}

#[cfg_attr(docsrs, doc(cfg(feature = "convenience-intos")))]
#[cfg(feature = "convenience-intos")]
impl Into<Vec<Exception>> for Exception {
    fn into(self) -> Vec<Exception> {
        [self].into()
    }
}

#[cfg_attr(docsrs, doc(cfg(feature = "convenience-intos")))]
#[cfg(feature = "convenience-intos")]
impl Into<Option<Vec<Exception>>> for Exception {
    fn into(self) -> Option<Vec<Exception>> {
        Some([self].into())
    }
}

/// Builder used to generate an [Exception] struct.
///
/// ```
/// use lemon_bugsnag_rs::error::payload::events::Exception;
///
/// // Generally, you get the builder this way, rather than creating an
/// // ExceptionBuilder struct manually.
/// let mut eb = Exception::builder();
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "optional-builders")))]
#[cfg(feature = "optional-builders")]
#[derive(Clone, Default)]
pub struct ExceptionBuilder {
    exception: Exception,
    error_class: Option<String>,
    stack_trace: Option<Vec<StackTrace>>,
}

#[cfg_attr(docsrs, doc(cfg(feature = "optional-builders")))]
#[cfg(feature = "optional-builders")]
impl ExceptionBuilder {
    /// Set the error_class field of the Exception struct.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::Exception;
    ///
    /// let mut eb = Exception::builder();
    ///
    /// // Give the error_class a value.
    /// eb.set_error_class("Unhandled Error");
    ///
    /// // Unset the error_class.
    /// eb.set_error_class(None);
    /// ```
    pub fn set_error_class<'a>(
        &mut self,
        error_class: impl Into<Option<&'a str>>,
    ) -> &mut Self {
        self.error_class = error_class.into().map(|item| item.into());

        self
    }

    /// Set the message field of the Exception struct.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::Exception;
    ///
    /// let mut eb = Exception::builder();
    ///
    /// // Give the message a value.
    /// eb.set_message("Unhandled exception occured");
    ///
    /// // Unset the message.
    /// eb.set_message(None);
    /// ```
    pub fn set_message<'a>(
        &mut self,
        message: impl Into<Option<&'a str>>,
    ) -> &mut Self {
        self.exception.message = message.into().map(|item| item.into());

        self
    }

    /// Set the stack_trace field of the Exception struct.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::Exception;
    /// use lemon_bugsnag_rs::error::payload::events::StackTrace;
    ///
    /// let mut eb = Exception::builder();
    /// let stack_trace = StackTrace::default();
    ///
    /// // Give the stack_trace a value.
    /// eb.set_stack_trace(stack_trace);
    ///
    /// // With the convenience-intos feature disabled
    /// // eb.set_stack_trace([stack_trace].to_vec());
    ///
    /// // Unset the stack_trace.
    /// eb.set_stack_trace(None);
    /// ```
    pub fn set_stack_trace(
        &mut self,
        stack_trace: impl Into<Option<Vec<StackTrace>>>,
    ) -> &mut Self {
        self.stack_trace = stack_trace.into();

        self
    }

    /// Append one or more StackTrace structs to the current vector of
    /// StackTraces.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::Exception;
    /// use lemon_bugsnag_rs::error::payload::events::StackTrace;
    ///
    /// let mut eb = Exception::builder();
    /// let stack_trace = StackTrace::default();
    /// // Assume this is a different stack trace
    /// let stack_trace_2 = StackTrace::default();
    ///
    /// // Give the stack_trace a value.
    /// eb.set_stack_trace(stack_trace);
    ///
    /// // Push a new stack trace struct to stack_trace.
    /// eb.push_stack_trace(stack_trace_2);
    ///
    /// // With the convenience-intos feature disabled
    /// // eb.push_stack_trace([stack_trace_2].to_vec());
    ///
    /// // Unset the stack_trace.
    /// eb.set_stack_trace(None);
    /// ```
    pub fn push_stack_trace(
        &mut self,
        stack_trace: impl Into<Vec<StackTrace>>,
    ) -> &mut Self {
        self.prep_stack_trace();

        self.stack_trace
            .as_mut()
            .unwrap()
            .extend(stack_trace.into());

        self
    }

    /// TODO: Test
    fn prep_stack_trace(&mut self) -> &mut Self {
        if self.stack_trace.is_none() {
            self.stack_trace = Some(Vec::new());
        }

        self
    }

    /// Set the runtime_type field of the Exception struct.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::Exception;
    /// use lemon_bugsnag_rs::error::payload::events::enums::RuntimeType;
    ///
    /// let mut eb = Exception::builder();
    ///
    /// // Give the stack_trace a value.
    /// eb.set_runtime_type(RuntimeType::Go);
    ///
    /// // Unset the stack_trace.
    /// eb.set_runtime_type(None);
    /// ```
    pub fn set_runtime_type(
        &mut self,
        runtime_type: impl Into<Option<RuntimeType>>,
    ) -> &mut Self {
        self.exception.runtime_type = runtime_type.into();

        self
    }

    /// Generate an [Exception] struct using the options you set using the
    /// builder.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::Exception;
    /// use lemon_bugsnag_rs::error::payload::events::enums::RuntimeType;
    /// use lemon_bugsnag_rs::error::payload::events::StackTrace;
    ///
    /// let mut eb = Exception::builder();
    /// let stack_trace = StackTrace::default();
    ///
    /// eb.set_error_class("Unhandled Error")
    ///     .set_message("Unhandled Error exception occured in Line 13")
    ///     .set_stack_trace(stack_trace)
    ///     .set_runtime_type(RuntimeType::Go);
    ///
    /// let exception = eb.build();
    /// ```
    pub fn build(&mut self) -> Result<Exception, BuilderError> {
        let mut missing_fields: Vec<String> = Vec::new();

        if self.error_class.is_none() {
            missing_fields.push("error_class".to_string());
        }

        if self.stack_trace.is_none() {
            missing_fields.push("stack_trace".to_string());
        }

        if missing_fields.len() > 0 {
            Err(BuilderError::MissingField {
                context: "ExceptionBuilder".to_string(),
                fields: missing_fields,
            })
        } else {
            self.exception.error_class = self.error_class.clone().unwrap();
            self.exception.stack_trace = self.stack_trace.clone().unwrap();

            Ok(self.exception.clone())
        }
    }
}

#[cfg(test)]
mod test {
    #[cfg(feature = "convenience-intos")]
    mod test_convenience_intos {
        use crate::error::payload::events::Exception;

        #[test]
        pub fn test_exception_into_vec_exception() {
            let test_exception = Exception {
                error_class: "Error Class".to_string(),
                message: "Message".to_string().into(),
                stack_trace: Vec::new(),
                runtime_type: None,
            };

            let vec_exception: Vec<Exception> = test_exception.clone().into();

            assert_eq!(vec![test_exception], vec_exception);
        }
    }

    #[cfg(feature = "optional-builders")]
    mod test_optional_builders {
        use crate::error::payload::events::{
            enums::RuntimeType, Exception, StackTrace, StackTraceCode,
        };
        use std::collections::HashMap;

        #[test]
        pub fn test_event_exceptions() {
            let mut test_code_map: HashMap<usize, String> = HashMap::new();
            test_code_map.insert(1, "Error".into());
            test_code_map.insert(2, "Another Error".to_string());

            let test_stack_trace_code: StackTraceCode =
                test_code_map.clone().into();

            let test_stack_trace = StackTrace {
                file: "Filename".to_string(),
                line_number: 12,
                column_number: 24.into(),
                method: "Function".to_string(),
                in_project: true.into(),
                code: test_stack_trace_code.clone().into(),
                frame_address: "Frame Address".to_string().into(),
                load_address: "Load Address".to_string().into(),
                is_lr: true.into(),
                is_pc: false.into(),
                symbol_address: "Symbol Address".to_string().into(),
                macho_file: "Macho File".to_string().into(),
                macho_load_address: "Macho Load Address".to_string().into(),
                macho_uuid: "Macho UUID".to_string().into(),
                macho_vm_address: "Macho VM Adress".to_string().into(),
                code_identifier: "Code Identifier".to_string().into(),
            };

            let test_exception = Exception {
                error_class: "Error Class".to_string(),
                message: "Message".to_string().into(),
                stack_trace: vec![test_stack_trace],
                runtime_type: RuntimeType::BrowserJs.into(),
            };

            let exception = Exception::builder()
                .set_error_class(test_exception.error_class.as_str())
                .set_message(test_exception.message.as_deref())
                .set_stack_trace(test_exception.stack_trace.clone())
                .set_runtime_type(test_exception.runtime_type.clone())
                .build()
                .unwrap();

            assert_eq!(test_exception, exception);
        }
    }
}