lemon_bugsnag_rs 0.1.2

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

/// Builder for creating [StackTraceCode] structs.
///
/// ```
/// use lemon_bugsnag_rs::error::payload::events::StackTraceCode;
///
/// let mut sb = StackTraceCode::builder();
/// sb.add(2, "A line of code goes here.")
///     .add(3, "Another line of code goes here.")
///     .add(4, "Can you guess what goes here?");
///
/// let stack_trace = sb.build();
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "optional-builders")))]
#[cfg(feature = "optional-builders")]
#[derive(Debug)]
pub struct StackTraceCodeBuilder {
    code: HashMap<usize, String>,
}

#[cfg_attr(docsrs, doc(cfg(feature = "optional-builders")))]
#[cfg(feature = "optional-builders")]
impl StackTraceCodeBuilder {
    /// Add an entry to the stack trace code context.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::StackTraceCode;
    ///
    /// let mut sb = StackTraceCode::builder();
    /// sb.add(262, "if true { /* Some code that caused a problem */ }");
    /// ```
    pub fn add<'a>(
        &mut self,
        line_number: usize,
        code: impl Into<&'a str>,
    ) -> &mut Self {
        self.code.insert(line_number, code.into().to_string());

        self
    }

    /// Set the entire stack trace code context from a HashMap.
    ///
    /// ```
    /// use std::collections::HashMap;
    /// use lemon_bugsnag_rs::error::payload::events::StackTraceCode;
    ///
    /// let mut test_code_map: HashMap<usize, String> = HashMap::new();
    /// test_code_map.insert(1, "A line of code from near the site of the problem.".into());
    /// test_code_map.insert(2, "Another line of code from near the site of the problem.".to_string());
    ///
    /// let mut sb = StackTraceCode::builder();
    /// sb.set_code(test_code_map);
    /// ```
    pub fn set_code(&mut self, code: HashMap<usize, String>) -> &mut Self {
        self.code = code;

        self
    }

    /// Remove a line from the stack trace code context.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::StackTraceCode;
    ///
    /// let mut sb = StackTraceCode::builder();
    /// sb.add(2, "This line stays.")
    ///     .add(3, "This line goes.")
    ///     .add(4, "This line also stays.");
    ///
    /// sb.remove(3);
    /// # assert!(sb.get(3).is_none());
    /// # assert_eq!(sb.get(2).unwrap().as_str(), "This line stays.");
    /// # assert_eq!(sb.get(4).unwrap().as_str(), "This line also stays.");
    /// ```
    pub fn remove(&mut self, line_number: usize) -> &mut Self {
        self.code.remove(&line_number);

        self
    }

    /// Retrieve a line of code previously pushed onto the context stack.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::StackTraceCode;
    ///
    /// let mut sb = StackTraceCode::builder();
    /// sb.add(2, "Nothing to see, here.")
    ///     .add(3, "This is the line I want.");
    ///
    /// // For demonstration purposes, only. Remember that unwrap() can panic.
    /// let line = sb.get(3).unwrap();
    /// # assert_eq!(line, "This is the line I want.");
    /// ```
    pub fn get(&self, index: usize) -> Option<String> {
        match self.code.get(&index) {
            Some(code) => Some(code.clone()),
            None => None,
        }
    }

    /// Retrieves the current map of line_number:code pairs.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::StackTraceCode;
    ///
    /// let mut sb = StackTraceCode::builder();
    /// sb.add(2, "A line of code goes here.")
    ///     .add(3, "Another line of code goes here.")
    ///     .add(4, "Can you guess what goes here?");
    ///
    /// let code_lines = sb.code();
    /// ```
    pub fn code(&self) -> &HashMap<usize, String> {
        &self.code
    }

    /// Creates a [StackTraceCode] struct from the builder.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::StackTraceCode;
    ///
    /// let mut sb = StackTraceCode::builder();
    /// sb.add(2, "A line of code goes here.")
    ///     .add(3, "Another line of code goes here.")
    ///     .add(4, "Can you guess what goes here?");
    ///
    /// let stack_trace_code = sb.build();
    /// ```
    pub fn build(&self) -> StackTraceCode {
        StackTraceCode { code: self.code.clone() }
    }
}

/// A struct to hold context information about the code surrounding the
/// point where the stack trace was generated.
///
/// ```
/// use std::collections::HashMap;
/// use lemon_bugsnag_rs::error::payload::events::StackTraceCode;
///
/// let stack_trace_code = StackTraceCode {
///     code: HashMap::new()
/// };
/// ```
#[derive(Clone, Default, Debug, Serialize, Eq, PartialEq)]
pub struct StackTraceCode {
    /// A [HashMap] of `line number => code` pairs holding lines from the
    /// code surrounding the source of the stack trace.
    #[serde(flatten)]
    pub code: HashMap<usize, String>,
}

#[cfg_attr(docsrs, doc(cfg(feature = "optional-builders")))]
#[cfg(feature = "optional-builders")]
impl StackTraceCode {
    /// Retrieve a [StackTraceCodeBuilder] to use for configuring and
    /// building a [StackTraceCode] struct.
    ///
    /// ```
    /// use lemon_bugsnag_rs::error::payload::events::StackTraceCode;
    ///
    /// let mut sb = StackTraceCode::builder();
    /// ```
    pub fn builder() -> StackTraceCodeBuilder {
        StackTraceCodeBuilder { code: HashMap::default() }
    }
}

impl From<&[(usize, &str)]> for StackTraceCode {
    fn from(value: &[(usize, &str)]) -> Self {
        let mut code: HashMap<usize, String> = HashMap::default();

        value.into_iter().for_each(|(line_number, line)| {
            code.insert(*line_number, line.to_string());
        });

        StackTraceCode { code }
    }
}

impl<T, J> From<HashMap<T, J>> for StackTraceCode
where
    HashMap<usize, std::string::String>: From<HashMap<T, J>>,
{
    fn from(value: HashMap<T, J>) -> Self {
        StackTraceCode { code: value.into() }
    }
}

#[cfg(feature = "optional-builders")]
#[cfg(test)]
mod tests {
    mod error_payload {
        use crate::error::payload::events::StackTraceCode;

        #[test]
        pub fn test_stack_trace_code_builder() {
            let mut builder = StackTraceCode::builder();
            builder
                .add(10, "PRINT HELLO")
                .add(20, "PRINT WORLD")
                .add(30, "GOTO 10");

            assert_eq!(builder.code().len(), 3);
            assert_eq!("PRINT HELLO", builder.get(10).unwrap());
            assert_eq!("PRINT WORLD", builder.get(20).unwrap());
            assert_eq!("GOTO 10", builder.get(30).unwrap());

            builder.add(30, "The end");
            assert_eq!(builder.code().len(), 3);
            assert_eq!("PRINT HELLO", builder.get(10).unwrap());
            assert_eq!("PRINT WORLD", builder.get(20).unwrap());
            assert_eq!("The end", builder.get(30).unwrap());
        }
    }
}