1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! Javascript console integration.
//! See the [ConsoleBackend] trait for more info.

use super::JsValue;

/// Log level of a log message sent via the console.
/// These levels represent the different functions defined in the spec:
/// <https://s3.amazonaws.com/temp.michaelfbryan.com/callbacks/index.html>
#[allow(missing_docs)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Level {
    Trace,
    Debug,
    Log,
    Info,
    Warn,
    Error,
}

impl std::fmt::Display for Level {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        use Level::*;
        let v = match self {
            Trace => "trace",
            Debug => "debug",
            Log => "log",
            Info => "info",
            Warn => "warn",
            Error => "error",
        };
        write!(f, "{}", v)
    }
}

/// A console backend that handles console messages sent from JS via
/// console.{log,debug,trace,...} functions.
///
/// A backend has to be registered via the `ContextBuilder::console` method.
///
/// A backend that forwads to the `log` crate is available with the `log` feature.
///
/// Note that any closure of type `Fn(Level, Vec<JsValue>)` implements this trait.
///
/// A very simple logger that just prints to stderr could look like this:
///
/// ```rust
/// use quick_js::{Context, JsValue, console::Level};
///
/// Context::builder()
///     .console(|level: Level, args: Vec<JsValue>| {
///         eprintln!("{}: {:?}", level, args);
///     })
///     .build()
///     # .unwrap();
/// ```
///
pub trait ConsoleBackend: std::panic::RefUnwindSafe + 'static {
    /// Handle a log message.
    fn log(&self, level: Level, values: Vec<JsValue>);
}

impl<F> ConsoleBackend for F
where
    F: Fn(Level, Vec<JsValue>) + std::panic::RefUnwindSafe + 'static,
{
    fn log(&self, level: Level, values: Vec<JsValue>) {
        (self)(level, values);
    }
}

#[cfg(feature = "log")]
mod log {
    use super::{JsValue, Level};

    /// A console implementation that logs messages via the `log` crate.
    ///
    /// Only available with the `log` feature.
    pub struct LogConsole;

    fn print_value(value: JsValue) -> String {
        match value {
            JsValue::Undefined => "undefined".to_string(),
            JsValue::Null => "null".to_string(),
            JsValue::Bool(v) => v.to_string(),
            JsValue::Int(v) => v.to_string(),
            JsValue::Float(v) => v.to_string(),
            JsValue::String(v) => v,
            JsValue::Array(values) => {
                let parts = values
                    .into_iter()
                    .map(print_value)
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("[{}]", parts)
            }
            JsValue::Object(map) => {
                let parts = map
                    .into_iter()
                    .map(|(key, value)| format!("{}: {}", key, print_value(value)))
                    .collect::<Vec<_>>()
                    .join(", ");
                format!("{{{}}}", parts)
            }
            #[cfg(feature = "chrono")]
            JsValue::Date(v) => v.to_string(),
            #[cfg(feature = "bigint")]
            JsValue::BigInt(v) => v.to_string(),
            JsValue::__NonExhaustive => unreachable!(),
        }
    }

    impl super::ConsoleBackend for LogConsole {
        fn log(&self, level: Level, values: Vec<JsValue>) {
            if values.is_empty() {
                return;
            }
            let log_level = match level {
                Level::Trace => log::Level::Trace,
                Level::Debug => log::Level::Debug,
                Level::Log => log::Level::Info,
                Level::Info => log::Level::Info,
                Level::Warn => log::Level::Warn,
                Level::Error => log::Level::Error,
            };

            let msg = values
                .into_iter()
                .map(print_value)
                .collect::<Vec<_>>()
                .join(" ");

            log::log!(log_level, "{}", msg);
        }
    }
}

#[cfg(feature = "log")]
pub use self::log::LogConsole;