use bytes::Bytes;
use libc::{c_char, c_uchar, size_t};
use pact_models::bodies::OptionalBody;
use pact_models::content_types::{ContentType, ContentTypeHint};
use pact_models::v4::http_parts::{HttpRequest, HttpResponse};
use pact_models::v4::message_parts::MessageContents;
use crate::{as_mut, as_ref, ffi_fn, safe_str};
use crate::models::generators::{GeneratorCategory, GeneratorCategoryIterator};
use crate::models::matching_rules::{MatchingRuleCategory, MatchingRuleCategoryIterator};
use crate::models::message::MessageMetadataIterator;
use crate::string::optional_str;
use crate::util::*;
ffi_fn! {
fn pactffi_message_contents_delete(contents: *const MessageContents) {
ptr::drop_raw(contents as *mut MessageContents);
}
}
ffi_fn! {
fn pactffi_message_contents_get_contents_str(contents: *const MessageContents) -> *const c_char {
let contents = as_ref!(contents);
match contents.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(contents.contents.value_as_string().unwrap_or_default().as_str())?;
content as *const c_char
}
}
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_message_contents_set_contents_str(contents: *mut MessageContents, contents_str: *const c_char, content_type: *const c_char) {
let contents = as_mut!(contents);
if contents_str.is_null() {
contents.contents = OptionalBody::Null;
} else {
let contents_str = safe_str!(contents_str);
let content_type = optional_str(content_type).map(|ct| ContentType::parse(ct.as_str()).ok()).flatten();
contents.contents = OptionalBody::Present(Bytes::from(contents_str), content_type, Some(ContentTypeHint::TEXT));
}
}
}
ffi_fn! {
fn pactffi_message_contents_get_contents_length(contents: *const MessageContents) -> size_t {
let contents = as_ref!(contents);
match &contents.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_contents_get_contents_bin(contents: *const MessageContents) -> *const c_uchar {
let contents = as_ref!(contents);
match &contents.contents {
OptionalBody::Empty | OptionalBody::Null | OptionalBody::Missing => std::ptr::null(),
OptionalBody::Present(bytes, _, _) => bytes.as_ptr()
}
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_message_contents_set_contents_bin(
contents: *mut MessageContents,
contents_bin: *const c_uchar,
len: size_t,
content_type: *const c_char
) {
let contents = as_mut!(contents);
if contents_bin.is_null() {
contents.contents = OptionalBody::Null;
} else {
let slice = unsafe { std::slice::from_raw_parts(contents_bin, len) };
let contents_bytes = Bytes::from(slice);
let content_type = optional_str(content_type).map(|ct| ContentType::parse(ct.as_str()).ok()).flatten();
contents.contents = OptionalBody::Present(contents_bytes, content_type, Some(ContentTypeHint::BINARY));
}
}
}
ffi_fn! {
fn pactffi_message_contents_get_metadata_iter(contents: *const MessageContents) -> *mut MessageMetadataIterator {
let contents = as_ref!(contents);
let iter = MessageMetadataIterator::new_from_contents(&contents);
ptr::raw_to(iter)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_message_contents_get_matching_rule_iter(
contents: *const MessageContents,
category: MatchingRuleCategory
) -> *mut MatchingRuleCategoryIterator {
let contents = as_ref!(contents);
let iter = MatchingRuleCategoryIterator::new_from_contents(&contents, category);
ptr::raw_to(iter)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_request_contents_get_matching_rule_iter(
request: *const HttpRequest,
category: MatchingRuleCategory
) -> *mut MatchingRuleCategoryIterator {
let request = as_ref!(request);
let iter = MatchingRuleCategoryIterator::new_from_request(&request, category);
ptr::raw_to(iter)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_response_contents_get_matching_rule_iter(
response: *const HttpResponse,
category: MatchingRuleCategory
) -> *mut MatchingRuleCategoryIterator {
let response = as_ref!(response);
let iter = MatchingRuleCategoryIterator::new_from_response(&response, category);
ptr::raw_to(iter)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_message_contents_get_generators_iter(
contents: *const MessageContents,
category: GeneratorCategory
) -> *mut GeneratorCategoryIterator {
let contents = as_ref!(contents);
let iter = GeneratorCategoryIterator::new_from_contents(&contents, category);
ptr::raw_to(iter)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_request_contents_get_generators_iter(
request: *const HttpRequest,
category: GeneratorCategory
) -> *mut GeneratorCategoryIterator {
let request = as_ref!(request);
let iter = GeneratorCategoryIterator::new_from_request(&request, category);
ptr::raw_to(iter)
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_response_contents_get_generators_iter(
response: *const HttpResponse,
category: GeneratorCategory
) -> *mut GeneratorCategoryIterator {
let response = as_ref!(response);
let iter = GeneratorCategoryIterator::new_from_response(&response, category);
ptr::raw_to(iter)
} {
std::ptr::null_mut()
}
}
#[cfg(test)]
mod tests {
use std::ffi::{CStr, CString};
use bytes::Bytes;
use expectest::prelude::*;
use libc::c_char;
use maplit::hashmap;
use pact_models::{generators, matchingrules};
use pact_models::bodies::OptionalBody;
use pact_models::content_types::JSON;
use pact_models::matchingrules::MatchingRule;
use pact_models::prelude::Generator;
use pact_models::v4::message_parts::MessageContents;
use serde_json::json;
use crate::models::contents::{
pactffi_message_contents_get_contents_str,
pactffi_message_contents_get_generators_iter,
pactffi_message_contents_get_matching_rule_iter,
pactffi_message_contents_get_metadata_iter
};
use crate::models::generators::{GeneratorCategory, pactffi_generator_to_json, pactffi_generators_iter_delete, pactffi_generators_iter_next, pactffi_generators_iter_pair_delete};
use crate::models::matching_rules::{
MatchingRuleCategory,
pactffi_matching_rule_to_json,
pactffi_matching_rules_iter_delete,
pactffi_matching_rules_iter_next,
pactffi_matching_rules_iter_pair_delete
};
use crate::models::message::{
pactffi_message_metadata_iter_delete,
pactffi_message_metadata_iter_next,
pactffi_message_metadata_pair_delete
};
#[test_log::test]
fn message_contents_feature_test() {
let json_contents = json!({
"a": "b",
"b": 100
});
let json_string = json_contents.to_string();
let json_bytes = Bytes::from(json_string.clone());
let message_contents = MessageContents {
contents: OptionalBody::Present(json_bytes, Some(JSON.clone()), None),
metadata: hashmap!{
"a".to_string() => json!("A"),
"b".to_string() => json!(100)
},
matching_rules: matchingrules! {
"body" => {
"$.a" => [ MatchingRule::Regex("\\w+".to_string()) ],
"$.b" => [ MatchingRule::Regex("\\d+".to_string()), MatchingRule::Number ]
}
},
generators: generators! {
"BODY" => {
"$.a" => Generator::RandomString(10),
"$.b" => Generator::RandomHexadecimal(10)
}
}
};
let message_contents_ptr = &message_contents as *const MessageContents;
let json_str_ptr = pactffi_message_contents_get_contents_str(message_contents_ptr);
let json_str = unsafe { CString::from_raw(json_str_ptr as *mut c_char) };
expect!(json_str.to_string_lossy()).to(be_equal_to(json_string.as_str()));
let metadata_iter_ptr = pactffi_message_contents_get_metadata_iter(message_contents_ptr);
expect!(metadata_iter_ptr.is_null()).to(be_false());
let first_pair = pactffi_message_metadata_iter_next(metadata_iter_ptr);
expect!(first_pair.is_null()).to(be_false());
let key = unsafe { CStr::from_ptr((*first_pair).key) };
let value = unsafe { CStr::from_ptr((*first_pair).value) };
expect!(key.to_string_lossy()).to(be_equal_to("a"));
expect!(value.to_string_lossy()).to(be_equal_to("A"));
pactffi_message_metadata_pair_delete(first_pair);
let second_pair = pactffi_message_metadata_iter_next(metadata_iter_ptr);
expect!(second_pair.is_null()).to(be_false());
let key = unsafe { CStr::from_ptr((*second_pair).key) };
let value = unsafe { CStr::from_ptr((*second_pair).value) };
expect!(key.to_string_lossy()).to(be_equal_to("b"));
expect!(value.to_string_lossy()).to(be_equal_to("100"));
pactffi_message_metadata_pair_delete(second_pair);
let third_pair = pactffi_message_metadata_iter_next(metadata_iter_ptr);
expect!(third_pair.is_null()).to(be_true());
pactffi_message_metadata_iter_delete(metadata_iter_ptr);
let matching_rule_iter_pointer = pactffi_message_contents_get_matching_rule_iter(message_contents_ptr, MatchingRuleCategory::BODY);
expect!(matching_rule_iter_pointer.is_null()).to(be_false());
let mr_first_pair = pactffi_matching_rules_iter_next(matching_rule_iter_pointer);
expect!(mr_first_pair.is_null()).to(be_false());
let path = unsafe { CStr::from_ptr((*mr_first_pair).path) };
let rule = unsafe { CString::from_raw(pactffi_matching_rule_to_json((*mr_first_pair).rule) as *mut c_char) };
expect!(path.to_string_lossy()).to(be_equal_to("$.a"));
expect!(rule.to_string_lossy()).to(be_equal_to("{\"match\":\"regex\",\"regex\":\"\\\\w+\"}"));
pactffi_matching_rules_iter_pair_delete(mr_first_pair);
let mr_second_pair = pactffi_matching_rules_iter_next(matching_rule_iter_pointer);
expect!(mr_second_pair.is_null()).to(be_false());
let path = unsafe { CStr::from_ptr((*mr_second_pair).path) };
let rule = unsafe { CString::from_raw(pactffi_matching_rule_to_json((*mr_second_pair).rule) as *mut c_char) };
expect!(path.to_string_lossy()).to(be_equal_to("$.b"));
expect!(rule.to_string_lossy()).to(be_equal_to("{\"match\":\"regex\",\"regex\":\"\\\\d+\"}"));
pactffi_matching_rules_iter_pair_delete(mr_second_pair);
let mr_third_pair = pactffi_matching_rules_iter_next(matching_rule_iter_pointer);
expect!(mr_third_pair.is_null()).to(be_false());
let path = unsafe { CStr::from_ptr((*mr_third_pair).path) };
let rule = unsafe { CString::from_raw(pactffi_matching_rule_to_json((*mr_third_pair).rule) as *mut c_char) };
expect!(path.to_string_lossy()).to(be_equal_to("$.b"));
expect!(rule.to_string_lossy()).to(be_equal_to("{\"match\":\"number\"}"));
pactffi_matching_rules_iter_pair_delete(mr_third_pair);
let mr_fouth_pair = pactffi_matching_rules_iter_next(matching_rule_iter_pointer);
expect!(mr_fouth_pair.is_null()).to(be_true());
pactffi_matching_rules_iter_delete(matching_rule_iter_pointer);
let generator_iter_pointer = pactffi_message_contents_get_generators_iter(message_contents_ptr, GeneratorCategory::BODY);
expect!(generator_iter_pointer.is_null()).to(be_false());
let gen_first_pair = pactffi_generators_iter_next(generator_iter_pointer);
expect!(gen_first_pair.is_null()).to(be_false());
let path = unsafe { CStr::from_ptr((*gen_first_pair).path) };
let generator = unsafe { CString::from_raw(pactffi_generator_to_json((*gen_first_pair).generator) as *mut c_char) };
expect!(path.to_string_lossy()).to(be_equal_to("$.a"));
expect!(generator.to_string_lossy()).to(be_equal_to("{\"size\":10,\"type\":\"RandomString\"}"));
pactffi_generators_iter_pair_delete(gen_first_pair);
let gen_second_pair = pactffi_generators_iter_next(generator_iter_pointer);
expect!(gen_second_pair.is_null()).to(be_false());
let path = unsafe { CStr::from_ptr((*gen_second_pair).path) };
let generator = unsafe { CString::from_raw(pactffi_generator_to_json((*gen_second_pair).generator) as *mut c_char) };
expect!(path.to_string_lossy()).to(be_equal_to("$.b"));
expect!(generator.to_string_lossy()).to(be_equal_to("{\"digits\":10,\"type\":\"RandomHexadecimal\"}"));
pactffi_generators_iter_pair_delete(gen_second_pair);
let mr_third_pair = pactffi_generators_iter_next(generator_iter_pointer);
expect!(mr_third_pair.is_null()).to(be_true());
pactffi_generators_iter_delete(generator_iter_pointer);
}
}