@@ -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