use std::convert::TryFrom;
use std::ffi::{CStr, CString};
use std::ptr;
use std::str::from_utf8;
use libc::{c_char, c_int};
use log::{error, LevelFilter as LogLevelFilter};
use pact_matching::logging::fetch_buffer_contents;
use crate::error::set_error_msg;
use crate::log::level_filter::LevelFilter;
use crate::log::logger::{add_sink, apply_logger, init_logger};
use crate::log::sink::Sink;
use crate::log::status::Status;
use crate::util::string::to_c;
#[no_mangle]
pub extern "C" fn pactffi_log_to_stdout(level_filter: LevelFilter) -> c_int {
pactffi_logger_init();
let spec = match CString::new("stdout") {
Ok(spec) => spec,
Err(e) => {
set_error_msg(e.to_string());
return Status::CantConstructSink as c_int;
}
};
let status = unsafe { pactffi_logger_attach_sink(spec.as_ptr(), level_filter) };
if status != 0 {
return status;
}
let status = pactffi_logger_apply();
if status != 0 {
return status;
}
Status::Success as c_int
}
#[no_mangle]
pub extern "C" fn pactffi_log_to_stderr(level_filter: LevelFilter) -> c_int {
pactffi_logger_init();
let spec = match CString::new("stderr") {
Ok(spec) => spec,
Err(e) => {
set_error_msg(e.to_string());
return Status::CantConstructSink as c_int;
}
};
let status = unsafe { pactffi_logger_attach_sink(spec.as_ptr(), level_filter) };
if status != 0 {
return status;
}
let status = pactffi_logger_apply();
if status != 0 {
return status;
}
Status::Success as c_int
}
#[no_mangle]
pub unsafe extern "C" fn pactffi_log_to_file(
file_name: *const c_char,
level_filter: LevelFilter,
) -> c_int {
pactffi_logger_init();
let spec = {
if file_name.is_null() {
return Status::CantConstructSink as c_int;
}
let file_name =
match CStr::from_ptr(file_name).to_str() {
Ok(file_name) => file_name,
Err(e) => {
set_error_msg(e.to_string());
return Status::CantConstructSink as c_int;
}
};
let spec = format!("file {}", file_name);
match CString::new(spec) {
Ok(spec) => spec,
Err(e) => {
set_error_msg(e.to_string());
return Status::CantConstructSink as c_int;
}
}
};
let status = pactffi_logger_attach_sink(spec.as_ptr(), level_filter);
if status != 0 {
return status;
}
let status = pactffi_logger_apply();
if status != 0 {
return status;
}
Status::Success as c_int
}
#[no_mangle]
pub extern "C" fn pactffi_log_to_buffer(level_filter: LevelFilter) -> c_int {
pactffi_logger_init();
let spec = match CString::new("buffer") {
Ok(spec) => spec,
Err(e) => {
set_error_msg(e.to_string());
return Status::CantConstructSink as c_int;
}
};
let status = unsafe { pactffi_logger_attach_sink(spec.as_ptr(), level_filter) };
if status != 0 {
return status;
}
let status = pactffi_logger_apply();
if status != 0 {
return status;
}
Status::Success as c_int
}
#[no_mangle]
pub extern "C" fn pactffi_logger_init() {
init_logger();
}
#[allow(clippy::missing_safety_doc)]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
pub unsafe extern "C" fn pactffi_logger_attach_sink(
sink_specifier: *const c_char,
level_filter: LevelFilter,
) -> c_int {
let sink_specifier = CStr::from_ptr(sink_specifier);
let sink_specifier = match sink_specifier.to_str() {
Ok(sink_specifier) => sink_specifier,
Err(_) => return Status::SpecifierNotUtf8 as c_int,
};
if let Err(err) = Sink::try_from(sink_specifier) {
return Status::from(err) as c_int;
}
let level_filter: LogLevelFilter = level_filter.into();
let status = match add_sink(sink_specifier, level_filter) {
Ok(_) => Status::Success,
Err(err) => Status::from(err),
};
status as c_int
}
#[no_mangle]
pub extern "C" fn pactffi_logger_apply() -> c_int {
let status = match apply_logger() {
Ok(_) => Status::Success,
Err(err) => Status::from(err),
};
status as c_int
}
#[no_mangle]
pub unsafe extern "C" fn pactffi_fetch_log_buffer(log_id: *const c_char) -> *const c_char {
let id = if log_id.is_null() {
"global"
} else {
CStr::from_ptr(log_id).to_str().unwrap_or("global")
};
match from_utf8(&fetch_buffer_contents(&id.to_string())) {
Ok(contents) => match to_c(contents) {
Ok(c_str) => c_str,
Err(err) => {
error!("Failed to copy in-memory log buffer - {}", err);
ptr::null()
}
}
Err(err) => {
error!("Failed to convert in-memory log buffer to UTF-8 = {}", err);
ptr::null()
}
}
}
#[cfg(test)]
mod tests {
use std::ffi::CString;
use expectest::prelude::*;
use crate::log::level_filter::LevelFilter;
use crate::log::pactffi_logger_attach_sink;
#[test]
fn pactffi_logger_attach_sink_with_log_level_off() {
let sink = CString::new("stderr").unwrap();
let result = unsafe {
pactffi_logger_attach_sink(sink.as_ptr(), LevelFilter::Off)
};
expect!(result).to(be_equal_to(0));
}
}