Skip to content

Commit b55407e

Browse files
committed
fix(FFI): pactffi_with_pact_metadata overwrites existing namespace contents #466
1 parent f3918b6 commit b55407e

1 file changed

Lines changed: 81 additions & 7 deletions

File tree

rust/pact_ffi/src/mock_server/handles.rs

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,11 +1123,14 @@ ffi_fn! {
11231123
}
11241124
}
11251125

1126-
/// Sets the additional metadata on the Pact file. Common uses are to add the client library details such as the name and version
1127-
/// Returns false if the interaction or Pact can't be modified (i.e. the mock server for it has already started)
1126+
const PROTECTED_NAMES: [&str; 2] = ["pactRust", "pactSpecification"];
1127+
1128+
/// Sets the additional metadata on the Pact file. Common uses are to add the client library
1129+
/// details such as the name and version. Returns false if the interaction or Pact can't be
1130+
/// modified (i.e. the mock server for it has already started) or the namespace is readonly.
11281131
///
11291132
/// * `pact` - Handle to a Pact model
1130-
/// * `namespace` - the top level metadat key to set any key values on
1133+
/// * `namespace` - the top level metadata key to set any key values on
11311134
/// * `name` - the key to set
11321135
/// * `value` - the value to set
11331136
#[no_mangle]
@@ -1143,11 +1146,29 @@ pub extern fn pactffi_with_pact_metadata(
11431146
let value = convert_cstr("value", value).unwrap_or_default();
11441147

11451148
if !namespace.is_empty() {
1146-
inner.pact.metadata.insert(namespace.to_string(), json!({ name: value }));
1149+
if PROTECTED_NAMES.contains(&namespace) {
1150+
warn!("'{}' is a readonly namespace and can't be modified", namespace);
1151+
false
1152+
} else {
1153+
match inner.pact.metadata.entry(namespace.to_string()) {
1154+
std::collections::btree_map::Entry::Vacant(entry) => {
1155+
entry.insert(json!({ name: value }));
1156+
}
1157+
std::collections::btree_map::Entry::Occupied(mut entry) => {
1158+
let metadata_entry = entry.get_mut();
1159+
if let Some(md) = metadata_entry.as_object_mut() {
1160+
md.insert(name.to_string(), Value::String(value.to_string()));
1161+
} else {
1162+
*metadata_entry = json!({ name: value });
1163+
}
1164+
}
1165+
}
1166+
!inner.mock_server_started
1167+
}
11471168
} else {
11481169
warn!("no namespace provided for metadata {:?} => {:?}. Ignoring", name, value);
1170+
false
11491171
}
1150-
!inner.mock_server_started
11511172
}).unwrap_or(false)
11521173
}
11531174

@@ -4314,8 +4335,8 @@ mod tests {
43144335
});
43154336
}
43164337

4317-
/// See https://github.com/pact-foundation/pact-php/pull/626
4318-
/// and https://github.com/pact-foundation/pact-reference/pull/461
4338+
// See https://github.com/pact-foundation/pact-php/pull/626
4339+
// and https://github.com/pact-foundation/pact-reference/pull/461
43194340
#[test]
43204341
fn annotate_raw_body_branch() {
43214342
let pact_handle = PactHandle::new("Consumer", "Provider");
@@ -4349,4 +4370,57 @@ mod tests {
43494370
Some(Bytes::from("a=1&b=2&c=3"))
43504371
)
43514372
}
4373+
4374+
// Issue #466
4375+
#[test]
4376+
fn pactffi_with_pact_metadata_test() {
4377+
let pact_handle = PactHandle::new("Consumer", "Provider");
4378+
let namespace1 = CString::new("namespace1").unwrap();
4379+
let var_1 = CString::new("var_1").unwrap();
4380+
let value_1 = CString::new("value_1").unwrap();
4381+
let result_1 = pactffi_with_pact_metadata(pact_handle, namespace1.as_ptr(), var_1.as_ptr(), value_1.as_ptr());
4382+
let var_2 = CString::new("var_2").unwrap();
4383+
let value_2 = CString::new("value_2").unwrap();
4384+
let result_2 = pactffi_with_pact_metadata(pact_handle, namespace1.as_ptr(), var_2.as_ptr(), value_2.as_ptr());
4385+
let namespace2 = CString::new("namespace2").unwrap();
4386+
let result_3 = pactffi_with_pact_metadata(pact_handle, namespace2.as_ptr(), var_1.as_ptr(), value_1.as_ptr());
4387+
let result_4 = pactffi_with_pact_metadata(pact_handle, namespace2.as_ptr(), var_2.as_ptr(), value_2.as_ptr());
4388+
4389+
let pact = pact_handle.with_pact(&|_, inner| inner.pact.clone()).unwrap();
4390+
4391+
pactffi_free_pact_handle(pact_handle);
4392+
4393+
expect!(result_1).to(be_true());
4394+
expect!(result_2).to(be_true());
4395+
expect!(result_3).to(be_true());
4396+
expect!(result_4).to(be_true());
4397+
4398+
expect!(pact.metadata.get("namespace1").unwrap()).to(be_equal_to(&json!({
4399+
"var_1": "value_1",
4400+
"var_2": "value_2"
4401+
})));
4402+
expect!(pact.metadata.get("namespace2").unwrap()).to(be_equal_to(&json!({
4403+
"var_1": "value_1",
4404+
"var_2": "value_2"
4405+
})));
4406+
}
4407+
4408+
// Issue #466
4409+
#[test]
4410+
fn pactffi_with_pact_metadata_with_readonly_namespace() {
4411+
let pact_handle = PactHandle::new("Consumer", "Provider");
4412+
let namespace1 = CString::new("pactRust").unwrap();
4413+
let var_1 = CString::new("var_1").unwrap();
4414+
let value_1 = CString::new("value_1").unwrap();
4415+
let result_1 = pactffi_with_pact_metadata(pact_handle, namespace1.as_ptr(), var_1.as_ptr(), value_1.as_ptr());
4416+
let namespace2 = CString::new("pactSpecification").unwrap();
4417+
let result_2 = pactffi_with_pact_metadata(pact_handle, namespace2.as_ptr(), var_1.as_ptr(), value_1.as_ptr());
4418+
4419+
let pact = pact_handle.with_pact(&|_, inner| inner.pact.clone()).unwrap();
4420+
4421+
pactffi_free_pact_handle(pact_handle);
4422+
4423+
expect!(result_1).to(be_false());
4424+
expect!(result_2).to(be_false());
4425+
}
43524426
}

0 commit comments

Comments
 (0)