Documentation
use crate::prelude::*;

pub type YRes<T> = Result<T, YError>;

pub const INTERNAL_ERROR: &str = "INTERNAL_ERROR";

#[derive(Debug, Clone)]
pub struct YError {
    pub code: String,
    pub msg: String,
    pub module: String,
    pub file: String,
    pub line: u32,
    pub trace: Vec<String>,
}

impl YError {

    pub fn upgrade(mut self, err: YError) -> Self {
        self.code = err.code;
        self.msg = err.msg;
        self
    }

    pub fn code(mut self, code: &str) -> Self {
        self.code = code.to_string();
        self
    }

    pub fn msg(mut self, msg: &str) -> Self {
        self.msg = msg.to_string();
        self
    }

    pub fn trace(mut self, ctx: String) -> Self {
        self.trace.push(ctx);
        self
    }

    pub fn is_internal(&self) -> bool {
        self.code == INTERNAL_ERROR
    }

}

impl From<diesel::result::Error> for YError {
    fn from(err: diesel::result::Error) -> YError {
        err!("got diesel::result::Error").trace(ctx!("YError::From<diesel::result::Error>", err))
    }
}

pub trait Trace {
    fn upgrade(self, err: YError) -> Self;
    fn upgrade_if(self, code: &str, err: YError) -> Self;
    fn code(self, code: &str) -> Self;
    fn msg(self, msg: &str) -> Self;
    fn trace(self, ctx: String) -> Self;
}

impl<T> Trace for YRes<T> {

    fn upgrade(mut self, new_err: YError) -> Self {
        if let Err(err) = &mut self {
            err.code = new_err.code;
            err.msg = new_err.msg;
        }
        self
    }

    fn upgrade_if(mut self, code: &str, new_err: YError) -> Self {
        if let Err(err) = &mut self {
            if err.code == code {
                err.code = new_err.code;
                err.msg = new_err.msg;
            }
        }
        self
    }

    fn code(mut self, code: &str) -> Self {
        if let Err(err) = &mut self {
            err.code = code.to_string();
        }
        self
    }

    fn msg(mut self, msg: &str) -> Self {
        if let Err(err) = &mut self {
            err.msg = msg.to_string();
        }
        self
    }

    fn trace(mut self, ctx: String) -> Self {
        if let Err(err) = &mut self {
            err.trace.push(ctx)
        }
        self
    }

}

#[macro_export]
macro_rules! ctx {
    () => {
        format!("MODULE={}, FILE={}, LINE={}", module_path!(), file!(), line!())
    };
    ($process:literal) => {
        format!("{}, MODULE={}, FILE={}, LINE={}", $process, module_path!(), file!(), line!())
    };
    ($process:literal, $($value:expr),*) => {{
        let mut ctx = format!("{}, ", $process);
        $(
            let var_trace = format!("{}={:?}, ", stringify!($value), $value);
            ctx.push_str(&var_trace);
        )*
        ctx.push_str(&format!("MODULE={}, FILE={}, LINE={}", module_path!(), file!(), line!()));
        ctx
    }};
    ($($value:expr),*) => {{
        let mut ctx = String::new();
        $(
            let var_trace = format!("{}={:?}, ", stringify!($value), $value);
            ctx.push_str(&var_trace);
        )*
        ctx.push_str(&format!("MODULE={}, FILE={}, LINE={}", module_path!(), file!(), line!()));
        ctx
    }};
}

#[macro_export]
macro_rules! err {
    () => {
        YError { 
            code: INTERNAL_ERROR.to_string(), 
            msg: "".to_string(),
            module: module_path!().to_string(),
            file: file!().to_string(), 
            line: line!(),
            trace: vec![],
        }
    };
    ($msg:literal) => {
        YError { 
            code: INTERNAL_ERROR.to_string(), 
            msg: $msg.to_string(),
            module: module_path!().to_string(),
            file: file!().to_string(), 
            line: line!(),
            trace: vec![],
        }
    };
    ($msg:literal, $($arg:tt)*) => {
        YError { 
            code: INTERNAL_ERROR.to_string(), 
            msg: format!($msg, $($arg)*),
            module: module_path!().to_string(),
            file: file!().to_string(), 
            line: line!(),
            trace: vec![],
        }
    };
    ($code:literal:$msg:literal) => {
        YError { 
            code: $code.to_string(), 
            msg: $msg.to_string(),
            module: module_path!().to_string(),
            file: file!().to_string(), 
            line: line!(),
            trace: vec![],
        }
    };
    ($code:literal:$msg:literal, $($arg:tt)*) => {
        YError { 
            code: $code.to_string(), 
            msg: format!($msg, $($arg)*),
            module: module_path!().to_string(),
            file: file!().to_string(), 
            line: line!(),
            trace: vec![],
        }
    };
}