1
//! Logging functions, plus Rust versions of `g_return_if_fail()`.
2
//!
3
//! Glib's `g_return_if_fail()`, `g_warning()`, etc. are all C macros, so they cannot be
4
//! used from Rust.  This module defines equivalent functions or macros with an `rsvg_`
5
//! prefix, to be clear that they should only be used from the implementation of the C API
6
//! and not from the main Rust code of the library.
7

            
8
use glib::ffi::{g_log_structured_array, GLogField, G_LOG_LEVEL_CRITICAL, G_LOG_LEVEL_WARNING};
9
use glib::translate::*;
10

            
11
/*
12
  G_LOG_LEVEL_CRITICAL          = 1 << 3,
13
  G_LOG_LEVEL_WARNING           = 1 << 4,
14

            
15
#define g_critical(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, \
16
                                                   __FILE__, G_STRINGIFY (__LINE__), \
17
                                                   G_STRFUNC, __VA_ARGS__)
18
#define g_warning(...)  g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, \
19
                                                   __FILE__, G_STRINGIFY (__LINE__), \
20
                                                   G_STRFUNC, __VA_ARGS__)
21
  GLogField fields[] =
22
    {
23
      { "PRIORITY", log_level_to_priority (log_level), -1 },
24
      { "CODE_FILE", file, -1 },
25
      { "CODE_LINE", line, -1 },
26
      { "CODE_FUNC", func, -1 },
27
      /* Filled in later: */
28
      { "MESSAGE", NULL, -1 },
29
      /* If @log_domain is %NULL, we will not pass this field: */
30
      { "GLIB_DOMAIN", log_domain, -1 },
31
    };
32

            
33
  g_log_structured_array (log_level, fields, n_fields);
34
 */
35

            
36
/// Helper function for converting string literals to C char pointers.
37
#[macro_export]
38
macro_rules! rsvg_c_str {
39
    ($txt:expr) => {
40
        // SAFETY: it's important that the type we pass to `from_bytes_with_nul` is 'static,
41
        // so that the storage behind the returned pointer doesn't get freed while it's still
42
        // being used. We get that by only allowing string literals.
43
        std::ffi::CStr::from_bytes_with_nul(concat!($txt, "\0").as_bytes())
44
            .unwrap()
45
            .as_ptr()
46
    };
47
}
48

            
49
/// Helper for `rsvg_g_warning` and `rsvg_g_critical`
50
///
51
/// This simulates what in C would be a call to the g_warning() or g_critical()
52
/// macros, but with the underlying function g_log_structured_array().
53
///
54
/// If the implementation of g_warning() or g_critical() changes, we'll have
55
/// to change this function.
56
fn rsvg_g_log(level: glib::ffi::GLogLevelFlags, msg: &str) {
57
    // stolen from gmessages.c:log_level_to_priority()
58
    let priority = match level {
59
        G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL => c"4".as_ptr(),
60
        _ => unreachable!("please add another log level priority to rsvg_g_log()"),
61
    };
62

            
63
    let c_msg = msg.to_glib_none();
64
    let c_char_msg: *const libc::c_char = c_msg.0;
65

            
66
    // Glib's g_log_structured_standard() adds a few more fields for the source
67
    // file, line number, etc., but those are not terribly useful without a stack
68
    // trace.  So, we'll omit them.
69
    let fields = [
70
        GLogField {
71
            key: c"PRIORITY".as_ptr(),
72
            value: priority.cast(),
73
            length: -1,
74
        },
75
        GLogField {
76
            key: c"MESSAGE".as_ptr(),
77
            value: c_char_msg.cast(),
78
            length: msg.len() as _,
79
        },
80
        // This is the G_LOG_DOMAIN set from the Makefile
81
        GLogField {
82
            key: c"GLIB_DOMAIN".as_ptr(),
83
            value: c"librsvg".as_ptr().cast(),
84
            length: -1,
85
        },
86
    ];
87

            
88
    unsafe {
89
        g_log_structured_array(level, fields.as_ptr(), fields.len());
90
    }
91
}
92

            
93
/// Replacement for `g_warning()`.
94
///
95
/// Use this to signal an error condition in the following situations:
96
///
97
/// * The C API does not have an adequate error code for the error in question (and cannot
98
///   be changed to have one, for ABI compatibility reasons).
99
///
100
/// * Applications using the C API would be prone to ignoring an important error,
101
///   so it's best to have a warning on the console to at least have a hope of someone
102
///   noticing the error.
103
pub(crate) fn rsvg_g_warning(msg: &str) {
104
    rsvg_g_log(glib::ffi::G_LOG_LEVEL_WARNING, msg);
105
}
106

            
107
/// Replacement for `g_critical()`.
108
///
109
/// Use this to signal a programming error from the caller of the C API, like passing
110
/// incorrect arguments or calling the API out of order.  Rust code conventionally panics
111
/// in such situations, but C/Glib code does not, so it's best to "do nothing", print a
112
/// critical message, and return.  Development versions of GNOME will crash the program
113
/// if this happens; release versions will ignore the error.
114
pub(crate) fn rsvg_g_critical(msg: &str) {
115
    rsvg_g_log(glib::ffi::G_LOG_LEVEL_CRITICAL, msg);
116
}
117

            
118
/// Replacement for `g_return_if_fail()`.
119
// Once Rust has a function! macro that gives us the current function name, we
120
// can remove the $func_name argument.
121
#[macro_export]
122
macro_rules! rsvg_return_if_fail {
123
    {
124
        $func_name:ident;
125
        $($condition:expr,)+
126
    } => {
127
        $(
128
            if !$condition {
129
                glib::ffi::g_return_if_fail_warning(
130
                    c"librsvg".as_ptr(),
131
                    rsvg_c_str!(stringify!($func_name)),
132
                    rsvg_c_str!(stringify!($condition)),
133
                );
134
                return;
135
            }
136
        )+
137
    }
138
}
139

            
140
/// Replacement for `g_return_val_if_fail()`.
141
#[macro_export]
142
macro_rules! rsvg_return_val_if_fail {
143
    {
144
        $func_name:ident => $retval:expr;
145
        $($condition:expr,)+
146
    } => {
147
        $(
148
            if !$condition {
149
                glib::ffi::g_return_if_fail_warning(
150
                    c"librsvg".as_ptr(),
151
                    rsvg_c_str!(stringify!($func_name)),
152
                    rsvg_c_str!(stringify!($condition)),
153
                );
154
                return $retval;
155
            }
156
        )+
157
    }
158
}