use std::collections::HashMap;
use std::ops::Drop;
use std::sync::Mutex;
use anyhow::{anyhow, Context};
use bytes::Bytes;
use either::Either;
use itertools::Itertools;
use libc::{c_char, c_int, c_uchar, c_uint, EXIT_FAILURE, EXIT_SUCCESS, size_t};
use serde_json::from_str as from_json_str;
use serde_json::Value as JsonValue;
use tracing::trace;
use pact_models::bodies::OptionalBody;
use pact_models::content_types::{ContentType, ContentTypeHint};
use pact_models::interaction::Interaction;
use pact_models::json_utils::json_to_string;
use crate::{as_mut, as_ref, cstr, ffi_fn, safe_str};
use crate::models::pact_specification::PactSpecification;
use crate::util::*;
use crate::util::string::optional_str;
use pact_matching::generators::generate_message;
use pact_models::generators::GeneratorTestMode;
use maplit::hashmap;
use futures::executor::block_on;
pub use pact_models::message::Message;
pub use pact_models::provider_states::ProviderState;
use pact_models::v4::message_parts::MessageContents;
ffi_fn! {
fn pactffi_message_new() -> *mut Message {
let message = Message::default();
ptr::raw_to(message)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_message_new_from_json(
index: c_uint,
json_str: *const c_char,
spec_version: PactSpecification
) -> *mut Message {
let message = {
let index = index as usize;
let json_value: JsonValue = from_json_str(safe_str!(json_str))
.context("error parsing json_str as JSON")?;
let spec_version = spec_version.into();
Message::from_json(index, &json_value, &spec_version)
.map_err(|e| anyhow::anyhow!("{}", e))?
};
ptr::raw_to(message)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_message_new_from_body(body: *const c_char, content_type: *const c_char) -> *mut Message {
let body = cstr!(body)
.to_bytes()
.to_owned();
let content_type = ContentType::parse(safe_str!(content_type))
.map_err(|s| anyhow!("invalid content type '{}'", s))?;
let mut metadata = HashMap::new();
metadata.insert(String::from("contentType"), JsonValue::String(content_type.to_string()));
let contents = OptionalBody::Present(body.into(), Some(content_type), None);
let message = Message {
contents,
metadata,
.. Message::default()
};
ptr::raw_to(message)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_message_delete(message: *mut Message) {
ptr::drop_raw(message);
}
}
ffi_fn! {
fn pactffi_message_get_contents(message: *const Message) -> *const c_char {
let message = as_ref!(message);
let message = block_on(generate_message(&message, &GeneratorTestMode::Consumer, &hashmap!{}, &vec![], &hashmap!{}));
match message.contents {
OptionalBody::Missing => std::ptr::null(),
OptionalBody::Empty | OptionalBody::Null => {
let content = string::to_c("")?;
content as *const c_char
}
_ => {
let content = string::to_c(message.contents.value_as_string().unwrap_or_default().as_str())?;
content as *const c_char
}
}
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_message_set_contents(message: *mut Message, contents: *const c_char, content_type: *const c_char) {
let message = as_mut!(message);
if contents.is_null() {
message.contents = OptionalBody::Null;
} else {
let contents = safe_str!(contents);
let content_type = optional_str(content_type).map(|ct| ContentType::parse(ct.as_str()).ok()).flatten();
message.contents = OptionalBody::Present(Bytes::from(contents), content_type, Some(ContentTypeHint::TEXT));
}
}
}
ffi_fn! {
fn pactffi_message_get_contents_length(message: *const Message) -> size_t {
let message = as_ref!(message);
match &message.contents {
OptionalBody::Missing | OptionalBody::Empty | OptionalBody::Null => 0 as size_t,
OptionalBody::Present(bytes, _, _) => bytes.len() as size_t
}
} {
0 as size_t
}
}
ffi_fn! {
fn pactffi_message_get_contents_bin(message: *const Message) -> *const c_uchar {
let message = as_ref!(message);
match &message.contents {
OptionalBody::Empty | OptionalBody::Null | OptionalBody::Missing => std::ptr::null(),
OptionalBody::Present(bytes, _, _) => bytes.as_ptr()
}
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_message_set_contents_bin(message: *mut Message, contents: *const c_uchar, len: size_t, content_type: *const c_char) {
let message = as_mut!(message);
if contents.is_null() {
message.contents = OptionalBody::Null;
} else {
let slice = unsafe { std::slice::from_raw_parts(contents, len) };
let contents = Bytes::from(slice);
let content_type = optional_str(content_type).map(|ct| ContentType::parse(ct.as_str()).ok()).flatten();
message.contents = OptionalBody::Present(contents, content_type, Some(ContentTypeHint::BINARY));
}
}
}
ffi_fn! {
fn pactffi_message_get_description(message: *const Message) -> *const c_char {
let message = as_ref!(message);
let description = string::to_c(&message.description)?;
description as *const c_char
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_message_set_description(message: *mut Message, description: *const c_char) -> c_int {
let message = as_mut!(message);
let description = safe_str!(description);
message.description.clear();
message.description.push_str(description);
EXIT_SUCCESS
} {
EXIT_FAILURE
}
}
ffi_fn! {
fn pactffi_message_get_provider_state(message: *const Message, index: c_uint) -> *const ProviderState {
let message = as_ref!(message);
let index = index as usize;
let provider_state = message
.provider_states
.get(index)
.ok_or(anyhow!("index is out of bounds"))?;
provider_state as *const ProviderState
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_message_get_provider_state_iter(message: *mut Message) -> *mut ProviderStateIterator {
let message = as_mut!(message);
let iter = ProviderStateIterator::new(message);
ptr::raw_to(iter)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_provider_state_iter_next(iter: *mut ProviderStateIterator) -> *mut ProviderState {
let iter = as_mut!(iter);
let index = iter.next();
let guard = iter.message.lock().unwrap();
let message_ptr = unsafe { guard.as_mut() };
match message_ptr {
Some(message) => {
match message.provider_states_mut().get_mut(index) {
Some(provider_state) => provider_state as *mut ProviderState,
None => {
trace!("iter past the end of provider states");
std::ptr::null_mut()
}
}
}
None => std::ptr::null_mut()
}
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_provider_state_iter_delete(iter: *mut ProviderStateIterator) {
ptr::drop_raw(iter);
}
}
#[allow(missing_copy_implementations)]
#[allow(missing_debug_implementations)]
pub struct ProviderStateIterator {
current: usize,
message: Mutex<*mut dyn Interaction>
}
impl ProviderStateIterator {
pub fn new(interaction: *mut dyn Interaction) -> Self {
ProviderStateIterator { current: 0, message: Mutex::new(interaction) }
}
fn next(&mut self) -> usize {
let idx = self.current;
self.current += 1;
idx
}
}
ffi_fn! {
fn pactffi_message_find_metadata(message: *const Message, key: *const c_char) -> *const c_char {
let message = as_ref!(message);
let key = safe_str!(key);
let value = message.metadata.get(key).ok_or(anyhow::anyhow!("invalid metadata key"))?;
let value_ptr = string::to_c(value.as_str().unwrap_or_default())?;
value_ptr as *const c_char
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_message_insert_metadata(
message: *mut Message,
key: *const c_char,
value: *const c_char
) -> c_int {
let message = as_mut!(message);
let key = safe_str!(key);
let value = safe_str!(value);
match message.metadata.insert(key.to_string(), JsonValue::String(value.to_string())) {
None => HashMapInsertStatus::SuccessNew as c_int,
Some(_) => HashMapInsertStatus::SuccessOverwrite as c_int,
}
} {
HashMapInsertStatus::Error as c_int
}
}
ffi_fn! {
fn pactffi_message_metadata_iter_next(iter: *mut MessageMetadataIterator) -> *mut MessageMetadataPair {
let iter = as_mut!(iter);
let generated_metadata;
let metadata = match iter.message {
Either::Left(message) => {
let message = as_ref!(message);
generated_metadata = block_on(generate_message(message, &GeneratorTestMode::Consumer, &hashmap!{}, &vec![], &hashmap!{})).metadata;
&generated_metadata
}
Either::Right(contents) => {
let contents = as_ref!(contents);
&contents.metadata
}
};
match iter.next() {
Some(key) => {
let (key, value) = metadata
.get_key_value(key)
.ok_or(anyhow::anyhow!("iter provided invalid metadata key"))?;
let pair = MessageMetadataPair::new(key, json_to_string(&value).as_str())?;
ptr::raw_to(pair)
}
None => {
trace!("iter past the end of metadata");
std::ptr::null_mut()
}
}
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_message_get_metadata_iter(message: *mut Message) -> *mut MessageMetadataIterator {
let message = as_mut!(message);
let iter = MessageMetadataIterator {
keys: message.metadata.keys().sorted().cloned().collect(),
current: 0,
message: Either::Left(message as *const Message)
};
ptr::raw_to(iter)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_message_metadata_iter_delete(iter: *mut MessageMetadataIterator) {
ptr::drop_raw(iter);
}
}
ffi_fn! {
fn pactffi_message_metadata_pair_delete(pair: *mut MessageMetadataPair) {
ptr::drop_raw(pair);
}
}
#[derive(Debug)]
pub struct MessageMetadataIterator {
keys: Vec<String>,
current: usize,
message: Either<*const Message, *const MessageContents>
}
impl MessageMetadataIterator {
fn next(&mut self) -> Option<&String> {
let idx = self.current;
self.current += 1;
self.keys.get(idx)
}
pub fn new_from_contents(contents: &MessageContents) -> Self {
MessageMetadataIterator {
keys: contents.metadata.keys().sorted().cloned().collect(),
current: 0,
message: Either::Right(contents as *const MessageContents)
}
}
}
#[derive(Debug)]
#[repr(C)]
#[allow(missing_copy_implementations)]
pub struct MessageMetadataPair {
pub key: *const c_char,
pub value: *const c_char,
}
impl MessageMetadataPair {
fn new(
key: &str,
value: &str,
) -> anyhow::Result<MessageMetadataPair> {
Ok(MessageMetadataPair {
key: string::to_c(key)? as *const c_char,
value: string::to_c(value)? as *const c_char,
})
}
}
impl Drop for MessageMetadataPair {
fn drop(&mut self) {
string::pactffi_string_delete(self.key as *mut c_char);
string::pactffi_string_delete(self.value as *mut c_char);
}
}
enum HashMapInsertStatus {
SuccessNew = 0,
SuccessOverwrite = -1,
Error = -2,
}
#[cfg(test)]
mod tests {
use std::ffi::CString;
use expectest::prelude::*;
use libc::c_char;
use crate::models::message::{
pactffi_message_delete,
pactffi_message_get_contents,
pactffi_message_get_contents_length,
pactffi_message_new,
pactffi_message_set_contents
};
#[test]
fn get_and_set_message_contents() {
let message = pactffi_message_new();
let message_contents = CString::new("This is a string").unwrap();
pactffi_message_set_contents(message, message_contents.as_ptr(), std::ptr::null());
let contents = pactffi_message_get_contents(message) as *mut c_char;
let len = pactffi_message_get_contents_length(message);
let str = unsafe { CString::from_raw(contents) };
pactffi_message_delete(message);
expect!(str.to_str().unwrap()).to(be_equal_to("This is a string"));
expect!(len).to(be_equal_to(16));
}
}