Skip to main content

pact_plugin_driver/
plugin_models.rs

1//! Models for representing plugins
2
3use std::collections::HashMap;
4use std::fmt::{Display, Formatter};
5use std::sync::Arc;
6use std::sync::atomic::{AtomicUsize, Ordering};
7
8use anyhow::anyhow;
9use async_trait::async_trait;
10use prost::Message;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use tonic::codegen::InterceptedService;
14use tonic::metadata::{Ascii, MetadataValue};
15use tonic::service::Interceptor;
16use tonic::transport::Channel;
17use tonic::{Request, Status};
18use tracing::{debug, trace};
19
20use crate::child_process::ChildPluginProcess;
21use crate::proto::pact_plugin_client::PactPluginClient as PactPluginClientV1;
22use crate::proto::*;
23use crate::proto_v2::{self, pact_plugin_client::PactPluginClient as PactPluginClientV2};
24
25/// Type of plugin dependencies
26#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
27pub enum PluginDependencyType {
28  /// Required operating system package
29  OSPackage,
30  /// Dependency on another plugin
31  Plugin,
32  /// Dependency on a shared library
33  Library,
34  /// Dependency on an executable
35  Executable,
36}
37
38impl Default for PluginDependencyType {
39  fn default() -> Self {
40    PluginDependencyType::Plugin
41  }
42}
43
44/// Plugin dependency
45#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
46#[serde(rename_all = "camelCase")]
47pub struct PluginDependency {
48  /// Dependency name
49  pub name: String,
50  /// Dependency version (semver format)
51  pub version: Option<String>,
52  /// Type of dependency
53  #[serde(default)]
54  pub dependency_type: PluginDependencyType,
55}
56
57impl Display for PluginDependency {
58  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59    if let Some(version) = &self.version {
60      write!(f, "{}:{}", self.name, version)
61    } else {
62      write!(f, "{}:*", self.name)
63    }
64  }
65}
66
67/// Manifest of a plugin
68#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
69#[serde(rename_all = "camelCase")]
70pub struct PactPluginManifest {
71  /// Directory were the plugin was loaded from
72  #[serde(skip)]
73  pub plugin_dir: String,
74
75  /// Interface version supported by the plugin
76  pub plugin_interface_version: u8,
77
78  /// Plugin name
79  pub name: String,
80
81  /// Plugin version in semver format
82  pub version: String,
83
84  /// Type if executable of the plugin
85  pub executable_type: String,
86
87  /// Minimum required version for the executable type
88  pub minimum_required_version: Option<String>,
89
90  /// How to invoke the plugin
91  pub entry_point: String,
92
93  /// Additional entry points for other operating systems (i.e. requiring a .bat file for Windows)
94  #[serde(default)]
95  pub entry_points: HashMap<String, String>,
96
97  /// Parameters to pass into the command line
98  pub args: Option<Vec<String>>,
99
100  /// Dependencies required to invoke the plugin
101  pub dependencies: Option<Vec<PluginDependency>>,
102
103  /// Plugin specific config
104  #[serde(default)]
105  pub plugin_config: HashMap<String, Value>,
106}
107
108impl PactPluginManifest {
109  pub fn as_dependency(&self) -> PluginDependency {
110    PluginDependency {
111      name: self.name.clone(),
112      version: Some(self.version.clone()),
113      dependency_type: PluginDependencyType::Plugin,
114    }
115  }
116}
117
118impl Default for PactPluginManifest {
119  fn default() -> Self {
120    PactPluginManifest {
121      plugin_dir: "".to_string(),
122      plugin_interface_version: 1,
123      name: "".to_string(),
124      version: "".to_string(),
125      executable_type: "".to_string(),
126      minimum_required_version: None,
127      entry_point: "".to_string(),
128      entry_points: Default::default(),
129      args: None,
130      dependencies: None,
131      plugin_config: Default::default(),
132    }
133  }
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq)]
137pub enum PluginInterfaceVersion {
138  V1,
139  V2,
140}
141
142#[derive(Clone, Debug, PartialEq, Eq)]
143pub struct PluginInitRequest {
144  pub implementation: String,
145  pub version: String,
146  pub host_capabilities: Vec<String>,
147}
148
149#[derive(Clone, Debug, PartialEq)]
150pub struct PluginInitResponse {
151  pub catalogue: Vec<CatalogueEntry>,
152  pub plugin_capabilities: Vec<String>,
153}
154
155impl TryFrom<u8> for PluginInterfaceVersion {
156  type Error = anyhow::Error;
157
158  fn try_from(value: u8) -> Result<Self, Self::Error> {
159    match value {
160      1 => Ok(PluginInterfaceVersion::V1),
161      2 => Ok(PluginInterfaceVersion::V2),
162      _ => Err(anyhow!("Unsupported plugin interface version {}", value)),
163    }
164  }
165}
166
167enum PluginClient {
168  V1(PactPluginClientV1<InterceptedService<Channel, PactPluginInterceptor>>),
169  V2(PactPluginClientV2<InterceptedService<Channel, PactPluginInterceptor>>),
170}
171
172impl PluginClient {
173  fn convert_message<T, U>(message: T) -> Result<U, Status>
174  where
175    T: Message,
176    U: Message + Default,
177  {
178    U::decode(message.encode_to_vec().as_slice()).map_err(|err| {
179      Status::internal(format!(
180        "Failed to convert between plugin interface message versions: {}",
181        err
182      ))
183    })
184  }
185
186  async fn init_plugin(
187    &mut self,
188    request: PluginInitRequest,
189  ) -> Result<PluginInitResponse, Status> {
190    match self {
191      PluginClient::V1(client) => client
192        .init_plugin(Request::new(InitPluginRequest {
193          implementation: request.implementation,
194          version: request.version,
195        }))
196        .await
197        .map(|response| PluginInitResponse {
198          catalogue: response.into_inner().catalogue,
199          plugin_capabilities: vec![],
200        }),
201      PluginClient::V2(client) => client
202        .init_plugin(Request::new(proto_v2::InitPluginRequest {
203          implementation: request.implementation,
204          version: request.version,
205          host_capabilities: request.host_capabilities,
206        }))
207        .await
208        .and_then(|response| match response.into_inner().response {
209          Some(proto_v2::init_plugin_response::Response::Success(success)) => {
210            Ok(PluginInitResponse {
211              catalogue: success
212                .catalogue
213                .into_iter()
214                .map(Self::convert_message)
215                .collect::<Result<Vec<CatalogueEntry>, Status>>()?,
216              plugin_capabilities: success.plugin_capabilities,
217            })
218          }
219          Some(proto_v2::init_plugin_response::Response::Failure(failure)) => {
220            let mut error = failure.error;
221            if !failure.missing_host_capabilities.is_empty() {
222              error.push_str(" (missing host capabilities: ");
223              error.push_str(failure.missing_host_capabilities.join(", ").as_str());
224              error.push(')');
225            }
226            Err(Status::failed_precondition(error))
227          }
228          None => Err(Status::internal(
229            "Plugin returned an invalid V2 InitPlugin response",
230          )),
231        }),
232    }
233  }
234
235  async fn compare_contents(
236    &mut self,
237    request: CompareContentsRequest,
238  ) -> Result<CompareContentsResponse, Status> {
239    match self {
240      PluginClient::V1(client) => client
241        .compare_contents(Request::new(request))
242        .await
243        .map(|response| response.into_inner()),
244      PluginClient::V2(client) => client
245        .compare_contents(Request::new(Self::convert_message::<
246          _,
247          proto_v2::CompareContentsRequest,
248        >(request)?))
249        .await
250        .and_then(|response| Self::convert_message(response.into_inner())),
251    }
252  }
253
254  async fn configure_interaction(
255    &mut self,
256    request: ConfigureInteractionRequest,
257  ) -> Result<ConfigureInteractionResponse, Status> {
258    match self {
259      PluginClient::V1(client) => client
260        .configure_interaction(Request::new(request))
261        .await
262        .map(|response| response.into_inner()),
263      PluginClient::V2(client) => client
264        .configure_interaction(Request::new(Self::convert_message::<
265          _,
266          proto_v2::ConfigureInteractionRequest,
267        >(request)?))
268        .await
269        .and_then(|response| Self::convert_message(response.into_inner())),
270    }
271  }
272
273  async fn generate_content(
274    &mut self,
275    request: GenerateContentRequest,
276  ) -> Result<GenerateContentResponse, Status> {
277    match self {
278      PluginClient::V1(client) => client
279        .generate_content(Request::new(request))
280        .await
281        .map(|response| response.into_inner()),
282      PluginClient::V2(client) => client
283        .generate_content(Request::new(Self::convert_message::<
284          _,
285          proto_v2::GenerateContentRequest,
286        >(request)?))
287        .await
288        .and_then(|response| Self::convert_message(response.into_inner())),
289    }
290  }
291
292  async fn start_mock_server(
293    &mut self,
294    request: StartMockServerRequest,
295  ) -> Result<StartMockServerResponse, Status> {
296    match self {
297      PluginClient::V1(client) => client
298        .start_mock_server(Request::new(request))
299        .await
300        .map(|response| response.into_inner()),
301      PluginClient::V2(client) => client
302        .start_mock_server(Request::new(Self::convert_message::<
303          _,
304          proto_v2::StartMockServerRequest,
305        >(request)?))
306        .await
307        .and_then(|response| Self::convert_message(response.into_inner())),
308    }
309  }
310
311  async fn shutdown_mock_server(
312    &mut self,
313    request: ShutdownMockServerRequest,
314  ) -> Result<ShutdownMockServerResponse, Status> {
315    match self {
316      PluginClient::V1(client) => client
317        .shutdown_mock_server(Request::new(request))
318        .await
319        .map(|response| response.into_inner()),
320      PluginClient::V2(client) => client
321        .shutdown_mock_server(Request::new(Self::convert_message::<
322          _,
323          proto_v2::ShutdownMockServerRequest,
324        >(request)?))
325        .await
326        .and_then(|response| Self::convert_message(response.into_inner())),
327    }
328  }
329
330  async fn get_mock_server_results(
331    &mut self,
332    request: MockServerRequest,
333  ) -> Result<MockServerResults, Status> {
334    match self {
335      PluginClient::V1(client) => client
336        .get_mock_server_results(Request::new(request))
337        .await
338        .map(|response| response.into_inner()),
339      PluginClient::V2(client) => client
340        .get_mock_server_results(Request::new(Self::convert_message::<
341          _,
342          proto_v2::MockServerRequest,
343        >(request)?))
344        .await
345        .and_then(|response| Self::convert_message(response.into_inner())),
346    }
347  }
348
349  async fn prepare_interaction_for_verification(
350    &mut self,
351    request: VerificationPreparationRequest,
352  ) -> Result<VerificationPreparationResponse, Status> {
353    match self {
354      PluginClient::V1(client) => client
355        .prepare_interaction_for_verification(Request::new(request))
356        .await
357        .map(|response| response.into_inner()),
358      PluginClient::V2(client) => client
359        .prepare_interaction_for_verification(Request::new(Self::convert_message::<
360          _,
361          proto_v2::VerificationPreparationRequest,
362        >(request)?))
363        .await
364        .and_then(|response| Self::convert_message(response.into_inner())),
365    }
366  }
367
368  async fn verify_interaction(
369    &mut self,
370    request: VerifyInteractionRequest,
371  ) -> Result<VerifyInteractionResponse, Status> {
372    match self {
373      PluginClient::V1(client) => client
374        .verify_interaction(Request::new(request))
375        .await
376        .map(|response| response.into_inner()),
377      PluginClient::V2(client) => client
378        .verify_interaction(Request::new(Self::convert_message::<
379          _,
380          proto_v2::VerifyInteractionRequest,
381        >(request)?))
382        .await
383        .and_then(|response| Self::convert_message(response.into_inner())),
384    }
385  }
386
387  async fn update_catalogue(&mut self, request: Catalogue) -> Result<(), Status> {
388    match self {
389      PluginClient::V1(client) => client
390        .update_catalogue(Request::new(request))
391        .await
392        .map(|_| ()),
393      PluginClient::V2(client) => client
394        .update_catalogue(Request::new(
395          Self::convert_message::<_, proto_v2::Catalogue>(request)?,
396        ))
397        .await
398        .map(|_| ()),
399    }
400  }
401}
402
403/// Trait with remote-calling methods for a running plugin
404#[async_trait]
405pub trait PactPluginRpc {
406  /// Send an init request to the plugin process
407  async fn init_plugin(&mut self, request: PluginInitRequest)
408    -> anyhow::Result<PluginInitResponse>;
409
410  /// Send a compare contents request to the plugin process
411  async fn compare_contents(
412    &self,
413    request: CompareContentsRequest,
414  ) -> anyhow::Result<CompareContentsResponse>;
415
416  /// Send a configure contents request to the plugin process
417  async fn configure_interaction(
418    &self,
419    request: ConfigureInteractionRequest,
420  ) -> anyhow::Result<ConfigureInteractionResponse>;
421
422  /// Send a generate content request to the plugin
423  async fn generate_content(
424    &self,
425    request: GenerateContentRequest,
426  ) -> anyhow::Result<GenerateContentResponse>;
427
428  /// Start a mock server
429  async fn start_mock_server(
430    &self,
431    request: StartMockServerRequest,
432  ) -> anyhow::Result<StartMockServerResponse>;
433
434  /// Shutdown a running mock server
435  async fn shutdown_mock_server(
436    &self,
437    request: ShutdownMockServerRequest,
438  ) -> anyhow::Result<ShutdownMockServerResponse>;
439
440  /// Get the matching results from a running mock server
441  async fn get_mock_server_results(
442    &self,
443    request: MockServerRequest,
444  ) -> anyhow::Result<MockServerResults>;
445
446  /// Prepare an interaction for verification. This should return any data required to construct any request
447  /// so that it can be amended before the verification is run.
448  async fn prepare_interaction_for_verification(
449    &self,
450    request: VerificationPreparationRequest,
451  ) -> anyhow::Result<VerificationPreparationResponse>;
452
453  /// Execute the verification for the interaction.
454  async fn verify_interaction(
455    &self,
456    request: VerifyInteractionRequest,
457  ) -> anyhow::Result<VerifyInteractionResponse>;
458
459  /// Updates the catalogue. This will be sent when the core catalogue has been updated (probably by a plugin loading).
460  async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()>;
461}
462
463/// Running plugin details
464#[derive(Debug, Clone)]
465pub struct PactPlugin {
466  /// Manifest for this plugin
467  pub manifest: PactPluginManifest,
468
469  /// Interface version supported by the plugin
470  pub interface_version: PluginInterfaceVersion,
471
472  /// Running child process
473  pub child: Arc<ChildPluginProcess>,
474
475  /// Optional capabilities negotiated for this plugin instance
476  pub plugin_capabilities: Vec<String>,
477
478  /// Count of access to the plugin. If this is ever zero, the plugin process will be shutdown
479  access_count: Arc<AtomicUsize>,
480}
481
482#[async_trait]
483impl PactPluginRpc for PactPlugin {
484  /// Send an init request to the plugin process
485  async fn init_plugin(
486    &mut self,
487    request: PluginInitRequest,
488  ) -> anyhow::Result<PluginInitResponse> {
489    let mut client = self.get_plugin_client().await?;
490    client
491      .init_plugin(request)
492      .await
493      .map_err(anyhow::Error::from)
494  }
495
496  /// Send a compare contents request to the plugin process
497  async fn compare_contents(
498    &self,
499    request: CompareContentsRequest,
500  ) -> anyhow::Result<CompareContentsResponse> {
501    let mut client = self.get_plugin_client().await?;
502    client
503      .compare_contents(request)
504      .await
505      .map_err(anyhow::Error::from)
506  }
507
508  /// Send a configure contents request to the plugin process
509  async fn configure_interaction(
510    &self,
511    request: ConfigureInteractionRequest,
512  ) -> anyhow::Result<ConfigureInteractionResponse> {
513    let mut client = self.get_plugin_client().await?;
514    client
515      .configure_interaction(request)
516      .await
517      .map_err(anyhow::Error::from)
518  }
519
520  /// Send a generate content request to the plugin
521  async fn generate_content(
522    &self,
523    request: GenerateContentRequest,
524  ) -> anyhow::Result<GenerateContentResponse> {
525    let mut client = self.get_plugin_client().await?;
526    client
527      .generate_content(request)
528      .await
529      .map_err(anyhow::Error::from)
530  }
531
532  async fn start_mock_server(
533    &self,
534    request: StartMockServerRequest,
535  ) -> anyhow::Result<StartMockServerResponse> {
536    let mut client = self.get_plugin_client().await?;
537    client
538      .start_mock_server(request)
539      .await
540      .map_err(anyhow::Error::from)
541  }
542
543  async fn shutdown_mock_server(
544    &self,
545    request: ShutdownMockServerRequest,
546  ) -> anyhow::Result<ShutdownMockServerResponse> {
547    let mut client = self.get_plugin_client().await?;
548    client
549      .shutdown_mock_server(request)
550      .await
551      .map_err(anyhow::Error::from)
552  }
553
554  async fn get_mock_server_results(
555    &self,
556    request: MockServerRequest,
557  ) -> anyhow::Result<MockServerResults> {
558    let mut client = self.get_plugin_client().await?;
559    client
560      .get_mock_server_results(request)
561      .await
562      .map_err(anyhow::Error::from)
563  }
564
565  async fn prepare_interaction_for_verification(
566    &self,
567    request: VerificationPreparationRequest,
568  ) -> anyhow::Result<VerificationPreparationResponse> {
569    let mut client = self.get_plugin_client().await?;
570    client
571      .prepare_interaction_for_verification(request)
572      .await
573      .map_err(anyhow::Error::from)
574  }
575
576  async fn verify_interaction(
577    &self,
578    request: VerifyInteractionRequest,
579  ) -> anyhow::Result<VerifyInteractionResponse> {
580    let mut client = self.get_plugin_client().await?;
581    client
582      .verify_interaction(request)
583      .await
584      .map_err(anyhow::Error::from)
585  }
586
587  async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()> {
588    let mut client = self.get_plugin_client().await?;
589    client
590      .update_catalogue(request)
591      .await
592      .map_err(anyhow::Error::from)
593  }
594}
595
596impl PactPlugin {
597  /// Create a new Plugin
598  pub fn new(manifest: &PactPluginManifest, child: ChildPluginProcess) -> anyhow::Result<Self> {
599    Ok(PactPlugin {
600      manifest: manifest.clone(),
601      interface_version: PluginInterfaceVersion::try_from(manifest.plugin_interface_version)?,
602      child: Arc::new(child),
603      plugin_capabilities: vec![],
604      access_count: Arc::new(AtomicUsize::new(1)),
605    })
606  }
607
608  pub fn has_plugin_capability(&self, capability: &str) -> bool {
609    self.plugin_capabilities.iter().any(|value| value == capability)
610  }
611
612  /// Port the plugin is running on
613  pub fn port(&self) -> u16 {
614    self.child.port()
615  }
616
617  /// Kill the running plugin process
618  pub fn kill(&self) {
619    self.child.kill();
620  }
621
622  /// Update the access of the plugin
623  pub fn update_access(&mut self) {
624    let count = self.access_count.fetch_add(1, Ordering::SeqCst);
625    trace!(
626      "update_access: Plugin {}/{} access is now {}",
627      self.manifest.name,
628      self.manifest.version,
629      count + 1
630    );
631  }
632
633  /// Decrement and return the access count for the plugin
634  pub fn drop_access(&mut self) -> usize {
635    let check = self
636      .access_count
637      .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
638        if count > 0 { Some(count - 1) } else { None }
639      });
640    let count = if let Ok(v) = check {
641      if v > 0 { v - 1 } else { v }
642    } else {
643      0
644    };
645    trace!(
646      "drop_access: Plugin {}/{} access is now {}",
647      self.manifest.name, self.manifest.version, count
648    );
649    count
650  }
651
652  async fn connect_channel(&self) -> anyhow::Result<Channel> {
653    let port = self.child.port();
654    match Channel::from_shared(format!("http://[::1]:{}", port))?
655      .connect()
656      .await
657    {
658      Ok(channel) => Ok(channel),
659      Err(err) => {
660        debug!("IP6 connection failed, will try IP4 address - {err}");
661        Channel::from_shared(format!("http://127.0.0.1:{}", port))?
662          .connect()
663          .await
664          .map_err(|err| anyhow!(err))
665      }
666    }
667  }
668
669  async fn get_plugin_client(&self) -> anyhow::Result<PluginClient> {
670    let channel = self.connect_channel().await?;
671    let interceptor = PactPluginInterceptor::new(self.child.plugin_info.server_key.as_str())?;
672    match self.interface_version {
673      PluginInterfaceVersion::V1 => Ok(PluginClient::V1(PactPluginClientV1::with_interceptor(
674        channel,
675        interceptor,
676      ))),
677      PluginInterfaceVersion::V2 => Ok(PluginClient::V2(PactPluginClientV2::with_interceptor(
678        channel,
679        interceptor,
680      ))),
681    }
682  }
683}
684
685/// Interceptor to inject the server key as an authorisation header
686#[derive(Clone, Debug)]
687struct PactPluginInterceptor {
688  /// Server key to inject
689  server_key: MetadataValue<Ascii>,
690}
691
692impl PactPluginInterceptor {
693  fn new(server_key: &str) -> anyhow::Result<Self> {
694    let token = MetadataValue::try_from(server_key)?;
695    Ok(PactPluginInterceptor { server_key: token })
696  }
697}
698
699impl Interceptor for PactPluginInterceptor {
700  fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
701    request
702      .metadata_mut()
703      .insert("authorization", self.server_key.clone());
704    Ok(request)
705  }
706}
707
708/// Plugin configuration to add to the matching context for an interaction
709#[derive(Clone, Debug, PartialEq)]
710pub struct PluginInteractionConfig {
711  /// Global plugin config (Pact level)
712  pub pact_configuration: HashMap<String, Value>,
713  /// Interaction plugin config
714  pub interaction_configuration: HashMap<String, Value>,
715}
716
717#[cfg(test)]
718pub(crate) mod tests {
719  use std::collections::HashMap;
720  use std::sync::RwLock;
721
722  use async_trait::async_trait;
723  use tonic::Status;
724
725  use crate::plugin_models::{
726    PactPluginRpc, PluginClient, PluginInitRequest, PluginInitResponse,
727  };
728  use crate::proto::verification_preparation_response::Response;
729  use crate::proto::*;
730  use crate::proto_v2;
731
732  pub(crate) struct MockPlugin {
733    pub prepare_request: RwLock<VerificationPreparationRequest>,
734    pub verify_request: RwLock<VerifyInteractionRequest>,
735  }
736
737  impl Default for MockPlugin {
738    fn default() -> Self {
739      MockPlugin {
740        prepare_request: RwLock::new(VerificationPreparationRequest::default()),
741        verify_request: RwLock::new(VerifyInteractionRequest::default()),
742      }
743    }
744  }
745
746  #[test]
747  fn converts_between_v1_and_v2_messages() {
748    let request = PluginInitRequest {
749      implementation: "plugin-driver-rust".to_string(),
750      version: "1.0.0-beta.1".to_string(),
751      host_capabilities: vec!["interaction/request-response".to_string()],
752    };
753
754    let converted_request = proto_v2::InitPluginRequest {
755      implementation: request.implementation,
756      version: request.version,
757      host_capabilities: request.host_capabilities,
758    };
759    assert_eq!(converted_request.implementation, "plugin-driver-rust");
760    assert_eq!(converted_request.version, "1.0.0-beta.1");
761    assert_eq!(
762      converted_request.host_capabilities,
763      vec!["interaction/request-response"]
764    );
765
766    let response = proto_v2::InitPluginResponse {
767      response: Some(proto_v2::init_plugin_response::Response::Success(
768        proto_v2::InitPluginSuccess {
769          catalogue: vec![proto_v2::CatalogueEntry {
770            r#type: proto_v2::catalogue_entry::EntryType::ContentMatcher as i32,
771            key: "test".to_string(),
772            values: HashMap::new(),
773          }],
774          plugin_capabilities: vec!["plugin/verification".to_string()],
775        },
776      )),
777    };
778
779    let converted_response = match response.response.unwrap() {
780      proto_v2::init_plugin_response::Response::Success(success) => PluginInitResponse {
781        catalogue: success
782          .catalogue
783          .into_iter()
784          .map(PluginClient::convert_message)
785          .collect::<Result<Vec<CatalogueEntry>, Status>>()
786          .unwrap(),
787        plugin_capabilities: success.plugin_capabilities,
788      },
789      _ => unreachable!(),
790    };
791    assert_eq!(converted_response.catalogue.len(), 1);
792    assert_eq!(converted_response.catalogue[0].key, "test");
793    assert_eq!(
794      converted_response.catalogue[0].r#type,
795      catalogue_entry::EntryType::ContentMatcher as i32
796    );
797    assert_eq!(converted_response.plugin_capabilities, vec!["plugin/verification"]);
798  }
799
800  #[async_trait]
801  impl PactPluginRpc for MockPlugin {
802    async fn init_plugin(
803      &mut self,
804      _request: PluginInitRequest,
805    ) -> anyhow::Result<PluginInitResponse> {
806      unimplemented!()
807    }
808
809    async fn compare_contents(
810      &self,
811      _request: CompareContentsRequest,
812    ) -> anyhow::Result<CompareContentsResponse> {
813      unimplemented!()
814    }
815
816    async fn configure_interaction(
817      &self,
818      _request: ConfigureInteractionRequest,
819    ) -> anyhow::Result<ConfigureInteractionResponse> {
820      unimplemented!()
821    }
822
823    async fn generate_content(
824      &self,
825      _request: GenerateContentRequest,
826    ) -> anyhow::Result<GenerateContentResponse> {
827      unimplemented!()
828    }
829
830    async fn start_mock_server(
831      &self,
832      _request: StartMockServerRequest,
833    ) -> anyhow::Result<StartMockServerResponse> {
834      unimplemented!()
835    }
836
837    async fn shutdown_mock_server(
838      &self,
839      _request: ShutdownMockServerRequest,
840    ) -> anyhow::Result<ShutdownMockServerResponse> {
841      unimplemented!()
842    }
843
844    async fn get_mock_server_results(
845      &self,
846      _request: MockServerRequest,
847    ) -> anyhow::Result<MockServerResults> {
848      unimplemented!()
849    }
850
851    async fn prepare_interaction_for_verification(
852      &self,
853      request: VerificationPreparationRequest,
854    ) -> anyhow::Result<VerificationPreparationResponse> {
855      let mut w = self.prepare_request.write().unwrap();
856      *w = request;
857      let data = InteractionData {
858        body: None,
859        metadata: Default::default(),
860      };
861      Ok(VerificationPreparationResponse {
862        response: Some(Response::InteractionData(data)),
863      })
864    }
865
866    async fn verify_interaction(
867      &self,
868      request: VerifyInteractionRequest,
869    ) -> anyhow::Result<VerifyInteractionResponse> {
870      let mut w = self.verify_request.write().unwrap();
871      *w = request;
872      let result = VerificationResult {
873        success: false,
874        response_data: None,
875        mismatches: vec![],
876        output: vec![],
877      };
878      Ok(VerifyInteractionResponse {
879        response: Some(verify_interaction_response::Response::Result(result)),
880      })
881    }
882
883    async fn update_catalogue(&self, _request: Catalogue) -> anyhow::Result<()> {
884      unimplemented!()
885    }
886  }
887}