use std::ffi::CString;
use std::ptr::null;
use either::Either;
use libc::{c_char, c_int, EXIT_FAILURE, EXIT_SUCCESS};
use pact_models::generators::Generator;
use pact_models::matchingrules::expressions::{
is_matcher_def,
MatchingRuleDefinition,
parse_matcher_def,
ValueType
};
use pact_models::matchingrules::MatchingRule;
use pact_models::time_utils::validate_datetime;
use tracing::{debug, error, trace};
use crate::{as_mut, as_ref, ffi_fn, safe_str};
use crate::error::set_error_msg;
use crate::util::{ptr, string};
#[derive(Debug, Clone)]
pub struct MatchingRuleDefinitionResult {
result: Either<String, MatchingRuleDefinition>
}
ffi_fn! {
fn pactffi_parse_matcher_definition(expression: *const c_char) -> *const MatchingRuleDefinitionResult {
let expression = safe_str!(expression);
let result = if is_matcher_def(expression) {
match parse_matcher_def(expression) {
Ok(definition) => {
debug!("Parsed matcher definition '{}' to '{:?}'", expression, definition);
MatchingRuleDefinitionResult {
result: Either::Right(definition)
}
}
Err(err) => {
error!("Failed to parse matcher definition '{}': {}", expression, err);
MatchingRuleDefinitionResult {
result: Either::Left(err.to_string())
}
}
}
} else if expression.is_empty() {
MatchingRuleDefinitionResult {
result: Either::Left("Expected a matching rule definition, but got an empty string".to_string())
}
} else {
MatchingRuleDefinitionResult {
result: Either::Right(MatchingRuleDefinition {
value: expression.to_string(),
value_type: ValueType::String,
rules: vec![],
generator: None,
expression: expression.to_string()
})
}
};
ptr::raw_to(result) as *const MatchingRuleDefinitionResult
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_matcher_definition_error(definition: *const MatchingRuleDefinitionResult) -> *const c_char {
let definition = as_ref!(definition);
if let Either::Left(error) = &definition.result {
string::to_c(&error)? as *const c_char
} else {
std::ptr::null()
}
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_matcher_definition_value(definition: *const MatchingRuleDefinitionResult) -> *const c_char {
let definition = as_ref!(definition);
if let Either::Right(definition) = &definition.result {
string::to_c(&definition.value)? as *const c_char
} else {
std::ptr::null()
}
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_matcher_definition_delete(definition: *const MatchingRuleDefinitionResult) {
ptr::drop_raw(definition as *mut MatchingRuleDefinitionResult);
}
}
ffi_fn! {
fn pactffi_matcher_definition_generator(definition: *const MatchingRuleDefinitionResult) -> *const Generator {
let definition = as_ref!(definition);
if let Either::Right(definition) = &definition.result {
if let Some(generator) = &definition.generator {
generator as *const Generator
} else {
std::ptr::null()
}
} else {
std::ptr::null()
}
} {
std::ptr::null()
}
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ExpressionValueType {
Unknown,
String,
Number,
Integer,
Decimal,
Boolean
}
impl ExpressionValueType {
fn from_value_type(t: ValueType) -> ExpressionValueType {
match t {
ValueType::Unknown => ExpressionValueType::Unknown,
ValueType::String => ExpressionValueType::String,
ValueType::Number => ExpressionValueType::Number,
ValueType::Integer => ExpressionValueType::Integer,
ValueType::Decimal => ExpressionValueType::Decimal,
ValueType::Boolean => ExpressionValueType::Boolean
}
}
}
ffi_fn! {
fn pactffi_matcher_definition_value_type(definition: *const MatchingRuleDefinitionResult) -> ExpressionValueType {
let definition = as_ref!(definition);
if let Either::Right(definition) = &definition.result {
ExpressionValueType::from_value_type(definition.value_type)
} else {
ExpressionValueType::Unknown
}
} {
ExpressionValueType::Unknown
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MatchingRuleResult {
MatchingRule(u16, *const c_char, MatchingRule),
MatchingReference(*const c_char)
}
ffi_fn! {
fn pactffi_matching_rule_iter_delete(iter: *mut MatchingRuleIterator) {
ptr::drop_raw(iter);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum MatchingRuleIteratorInner {
MatchingRule(MatchingRule, Option<CString>, MatchingRuleResult),
MatchingReference(CString, MatchingRuleResult)
}
#[derive(Debug)]
pub struct MatchingRuleIterator {
current: usize,
rules: Vec<MatchingRuleIteratorInner>
}
impl MatchingRuleIterator {
pub fn new(definition: &MatchingRuleDefinition) -> Self {
MatchingRuleIterator {
current: 0,
rules: definition.rules.iter().map(|r| {
match r {
Either::Left(rule) => {
let val = match rule {
MatchingRule::Equality => None,
MatchingRule::Regex(s) => Some(CString::new(s.as_str()).unwrap()),
MatchingRule::Type => None,
MatchingRule::MinType(m) => Some(CString::new(m.to_string()).unwrap()),
MatchingRule::MaxType(m) => Some(CString::new(m.to_string()).unwrap()),
MatchingRule::MinMaxType(min, max) => {
let s = format!("{}:{}", min, max);
Some(CString::new(s).unwrap())
},
MatchingRule::Timestamp(s) => Some(CString::new(s.as_str()).unwrap()),
MatchingRule::Time(s) => Some(CString::new(s.as_str()).unwrap()),
MatchingRule::Date(s) => Some(CString::new(s.as_str()).unwrap()),
MatchingRule::Include(s) => Some(CString::new(s.as_str()).unwrap()),
MatchingRule::Number => None,
MatchingRule::Integer => None,
MatchingRule::Decimal => None,
MatchingRule::Null => None,
MatchingRule::ContentType(s) => Some(CString::new(s.as_str()).unwrap()),
MatchingRule::ArrayContains(_) => None,
MatchingRule::Values => None,
MatchingRule::Boolean => None,
MatchingRule::StatusCode(_) => None,
MatchingRule::NotEmpty => None,
MatchingRule::Semver => None,
MatchingRule::EachKey(_) => None,
MatchingRule::EachValue(_) => None
};
let rule_value = val.as_ref().map(|v| v.as_ptr()).unwrap_or_else(|| null());
let rule_result = MatchingRuleResult::MatchingRule(rule_id(rule), rule_value, rule.clone());
MatchingRuleIteratorInner::MatchingRule(rule.clone(), val, rule_result)
},
Either::Right(reference) => {
let name = CString::new(reference.name.as_str()).unwrap();
let p = name.as_ptr();
MatchingRuleIteratorInner::MatchingReference(name, MatchingRuleResult::MatchingReference(p))
}
}
}).collect()
}
}
fn next(&mut self) -> Option<&MatchingRuleResult> {
let idx = self.current;
self.current += 1;
self.rules.get(idx).map(|r| {
match r {
MatchingRuleIteratorInner::MatchingRule(_, _, c_val) => c_val,
MatchingRuleIteratorInner::MatchingReference(_, c_val) => c_val
}
})
}
}
fn rule_id(rule: &MatchingRule) -> u16 {
match rule {
MatchingRule::Equality => 1,
MatchingRule::Regex(_) => 2,
MatchingRule::Type => 3,
MatchingRule::MinType(_) => 4,
MatchingRule::MaxType(_) => 5,
MatchingRule::MinMaxType(_, _) => 6,
MatchingRule::Timestamp(_) => 7,
MatchingRule::Time(_) => 8,
MatchingRule::Date(_) => 9,
MatchingRule::Include(_) => 10,
MatchingRule::Number => 11,
MatchingRule::Integer => 12,
MatchingRule::Decimal => 13,
MatchingRule::Null => 14,
MatchingRule::ContentType(_) => 15,
MatchingRule::ArrayContains(_) => 16,
MatchingRule::Values => 17,
MatchingRule::Boolean => 18,
MatchingRule::StatusCode(_) => 19,
MatchingRule::NotEmpty => 20,
MatchingRule::Semver => 21,
MatchingRule::EachKey(_) => 22,
MatchingRule::EachValue(_) => 23
}
}
ffi_fn! {
fn pactffi_matcher_definition_iter(definition: *const MatchingRuleDefinitionResult) -> *mut MatchingRuleIterator {
let definition = as_ref!(definition);
if let Either::Right(result) = &definition.result {
let iter = MatchingRuleIterator::new(result);
ptr::raw_to(iter)
} else {
std::ptr::null_mut()
}
} {
std::ptr::null_mut()
}
}
ffi_fn! {
fn pactffi_matching_rule_iter_next(iter: *mut MatchingRuleIterator) -> *const MatchingRuleResult {
let iter = as_mut!(iter);
match iter.next() {
Some(result) => result as *const MatchingRuleResult,
None => {
trace!("iter past the end of matching rules");
std::ptr::null()
}
}
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_matching_rule_id(rule_result: *const MatchingRuleResult) -> u16 {
let rule_result = as_ref!(rule_result);
match rule_result {
MatchingRuleResult::MatchingRule(id, _, _) => *id,
MatchingRuleResult::MatchingReference(_) => 0
}
} {
0
}
}
ffi_fn! {
fn pactffi_matching_rule_value(rule_result: *const MatchingRuleResult) -> *const c_char {
let rule_result = as_ref!(rule_result);
match rule_result {
MatchingRuleResult::MatchingRule(_, value, _) => *value,
MatchingRuleResult::MatchingReference(_) => std::ptr::null()
}
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_matching_rule_pointer(rule_result: *const MatchingRuleResult) -> *const MatchingRule {
let rule_result = as_ref!(rule_result);
match rule_result {
MatchingRuleResult::MatchingRule(_, _, rule) => rule as *const MatchingRule,
MatchingRuleResult::MatchingReference(_) => std::ptr::null()
}
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_matching_rule_reference_name(rule_result: *const MatchingRuleResult) -> *const c_char {
let rule_result = as_ref!(rule_result);
match rule_result {
MatchingRuleResult::MatchingRule(_, _, _) => std::ptr::null(),
MatchingRuleResult::MatchingReference(ref_name) => *ref_name
}
} {
std::ptr::null()
}
}
ffi_fn! {
fn pactffi_validate_datetime(value: *const c_char, format: *const c_char) -> c_int {
let value = safe_str!(value);
let format = safe_str!(format);
if value.is_empty() {
error!("Date/Time value string is empty");
set_error_msg("Date/Time value string is empty".to_string());
EXIT_FAILURE
} else if format.is_empty() {
error!("Date/Time format string is empty");
set_error_msg("Date/Time format string is empty".to_string());
EXIT_FAILURE
} else {
match validate_datetime(value, format) {
Ok(_) => EXIT_SUCCESS,
Err(err) => {
error!("Date/Time string '{}' is not valid: {}", value, err);
set_error_msg(format!("Date/Time string '{}' does not match pattern '{}'", value, format));
EXIT_FAILURE
}
}
}
} {
2
}
}
#[cfg(test)]
mod tests {
use std::ffi::{CStr, CString};
use expectest::prelude::*;
use libc::c_char;
use pact_models::matchingrules::MatchingRule;
use crate::error::pactffi_get_error_message;
use crate::models::expressions::{
ExpressionValueType,
MatchingRuleDefinitionResult,
MatchingRuleResult,
pactffi_matcher_definition_error,
pactffi_matcher_definition_generator,
pactffi_matcher_definition_iter,
pactffi_matcher_definition_value,
pactffi_matcher_definition_value_type,
pactffi_matching_rule_id,
pactffi_matching_rule_iter_delete,
pactffi_matching_rule_iter_next,
pactffi_matching_rule_reference_name,
pactffi_matching_rule_value,
pactffi_parse_matcher_definition,
pactffi_validate_datetime
};
#[test_log::test]
fn parse_expression_with_null() {
let result = pactffi_parse_matcher_definition(std::ptr::null());
expect!(result.is_null()).to(be_true());
}
#[test_log::test]
fn parse_expression_with_empty_string() {
let empty = CString::new("").unwrap();
let result = pactffi_parse_matcher_definition(empty.as_ptr());
expect!(result.is_null()).to(be_false());
let error = pactffi_matcher_definition_error(result);
let string = unsafe { CString::from_raw(error as *mut c_char) };
expect!(string.to_string_lossy()).to(be_equal_to("Expected a matching rule definition, but got an empty string"));
let definition = unsafe { Box::from_raw(result as *mut MatchingRuleDefinitionResult) };
expect!(definition.result.left()).to(be_some().value("Expected a matching rule definition, but got an empty string"));
}
#[test_log::test]
fn parse_expression_with_invalid_expression() {
let value = CString::new("matching(type,").unwrap();
let result = pactffi_parse_matcher_definition(value.as_ptr());
expect!(result.is_null()).to(be_false());
let error = pactffi_matcher_definition_error(result);
let string = unsafe { CString::from_raw(error as *mut c_char) };
expect!(string.to_string_lossy().contains("expected a primitive value")).to(be_true());
let value = pactffi_matcher_definition_value(result);
expect!(value.is_null()).to(be_true());
let generator = pactffi_matcher_definition_generator(result);
expect!(generator.is_null()).to(be_true());
let value_type = pactffi_matcher_definition_value_type(result);
expect!(value_type).to(be_equal_to(ExpressionValueType::Unknown));
let definition = unsafe { Box::from_raw(result as *mut MatchingRuleDefinitionResult) };
expect!(definition.result.left().unwrap().contains("expected a primitive value")).to(be_true());
}
#[test_log::test]
fn parse_expression_with_valid_expression() {
let value = CString::new("matching(type,'Name')").unwrap();
let result = pactffi_parse_matcher_definition(value.as_ptr());
expect!(result.is_null()).to(be_false());
let error = pactffi_matcher_definition_error(result);
expect!(error.is_null()).to(be_true());
let value = pactffi_matcher_definition_value(result);
expect!(value.is_null()).to(be_false());
let string = unsafe { CString::from_raw(value as *mut c_char) };
expect!(string.to_string_lossy()).to(be_equal_to("Name"));
let generator = pactffi_matcher_definition_generator(result);
expect!(generator.is_null()).to(be_true());
let value_type = pactffi_matcher_definition_value_type(result);
expect!(value_type).to(be_equal_to(ExpressionValueType::String));
let iter = pactffi_matcher_definition_iter(result);
expect!(iter.is_null()).to(be_false());
let rule = pactffi_matching_rule_iter_next(iter);
expect!(rule.is_null()).to(be_false());
let r = unsafe { rule.as_ref() }.unwrap();
match r {
MatchingRuleResult::MatchingRule(id, v, rule) => {
expect!(*id).to(be_equal_to(3));
expect!(v.is_null()).to(be_true());
expect!(rule).to(be_equal_to(&MatchingRule::Type));
}
MatchingRuleResult::MatchingReference(_) => {
panic!("Expected a matching rule");
}
}
let rule_type = pactffi_matching_rule_id(rule);
expect!(rule_type).to(be_equal_to(3));
let rule_value = pactffi_matching_rule_value(rule);
expect!(rule_value.is_null()).to(be_true());
let ref_name = pactffi_matching_rule_reference_name(rule);
expect!(ref_name.is_null()).to(be_true());
let rule = pactffi_matching_rule_iter_next(iter);
expect!(rule.is_null()).to(be_true());
pactffi_matching_rule_iter_delete(iter);
let definition = unsafe { Box::from_raw(result as *mut MatchingRuleDefinitionResult) };
expect!(definition.result.as_ref().left()).to(be_none());
expect!(definition.result.as_ref().right()).to(be_some());
}
#[test_log::test]
fn parse_expression_with_normal_string() {
let value = CString::new("I am not an expression").unwrap();
let result = pactffi_parse_matcher_definition(value.as_ptr());
expect!(result.is_null()).to(be_false());
let error = pactffi_matcher_definition_error(result);
expect!(error.is_null()).to(be_true());
let value = pactffi_matcher_definition_value(result);
expect!(value.is_null()).to(be_false());
let string = unsafe { CString::from_raw(value as *mut c_char) };
expect!(string.to_string_lossy()).to(be_equal_to("I am not an expression"));
let value_type = pactffi_matcher_definition_value_type(result);
expect!(value_type).to(be_equal_to(ExpressionValueType::String));
let definition = unsafe { Box::from_raw(result as *mut MatchingRuleDefinitionResult) };
expect!(definition.result.as_ref().left()).to(be_none());
expect!(definition.result.as_ref().right()).to(be_some());
expect!(definition.result.as_ref().right().unwrap().rules.is_empty()).to(be_true());
}
#[test_log::test]
fn parse_expression_with_generator() {
let value = CString::new("matching(date,'yyyy-MM-dd', '2000-01-02')").unwrap();
let result = pactffi_parse_matcher_definition(value.as_ptr());
expect!(result.is_null()).to(be_false());
let error = pactffi_matcher_definition_error(result);
expect!(error.is_null()).to(be_true());
let value = pactffi_matcher_definition_value(result);
expect!(value.is_null()).to(be_false());
let string = unsafe { CString::from_raw(value as *mut c_char) };
expect!(string.to_string_lossy()).to(be_equal_to("2000-01-02"));
let iter = pactffi_matcher_definition_iter(result);
expect!(iter.is_null()).to(be_false());
let rule = pactffi_matching_rule_iter_next(iter);
expect!(rule.is_null()).to(be_false());
let rule_type = pactffi_matching_rule_id(rule);
expect!(rule_type).to(be_equal_to(9));
let rule_value = pactffi_matching_rule_value(rule);
let string = unsafe { CStr::from_ptr(rule_value) };
expect!(string.to_string_lossy()).to(be_equal_to("yyyy-MM-dd"));
pactffi_matching_rule_iter_delete(iter);
let generator = pactffi_matcher_definition_generator(result);
expect!(generator.is_null()).to(be_false());
let value_type = pactffi_matcher_definition_value_type(result);
expect!(value_type).to(be_equal_to(ExpressionValueType::String));
let definition = unsafe { Box::from_raw(result as *mut MatchingRuleDefinitionResult) };
expect!(definition.result.as_ref().left()).to(be_none());
expect!(definition.result.as_ref().right()).to(be_some());
}
#[test_log::test]
fn pactffi_validate_datetime_test() {
let value = CString::new("").unwrap();
let format = CString::new("yyyy-MM-dd").unwrap();
expect!(pactffi_validate_datetime(value.as_ptr(), format.as_ptr())).to(be_equal_to(1));
let mut buffer = Vec::with_capacity(256);
let pointer = buffer.as_mut_ptr();
pactffi_get_error_message(pointer, 256);
let error = unsafe { CStr::from_ptr(pointer) }.to_str().unwrap();
expect!(error).to(be_equal_to("Date/Time value string is empty"));
let value = CString::new("2000-02-28").unwrap();
let format = CString::new("yyyy-MM-dd").unwrap();
expect!(pactffi_validate_datetime(value.as_ptr(), format.as_ptr())).to(be_equal_to(0));
let value = CString::new("2000-02-x").unwrap();
let format = CString::new("yyyy-MM-dd").unwrap();
expect!(pactffi_validate_datetime(value.as_ptr(), format.as_ptr())).to(be_equal_to(1));
pactffi_get_error_message(pointer, 256);
let error = unsafe { CStr::from_ptr(pointer) }.to_str().unwrap();
expect!(error).to(be_equal_to("Date/Time string '2000-02-x' does not match pattern 'yyyy-MM-dd'"));
}
}