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  pub plugin_instance_id: String,
148}
149
150#[derive(Clone, Debug, PartialEq)]
151pub struct PluginInitResponse {
152  pub catalogue: Vec<CatalogueEntry>,
153  pub plugin_capabilities: Vec<String>,
154}
155
156impl TryFrom<u8> for PluginInterfaceVersion {
157  type Error = anyhow::Error;
158
159  fn try_from(value: u8) -> Result<Self, Self::Error> {
160    match value {
161      1 => Ok(PluginInterfaceVersion::V1),
162      2 => Ok(PluginInterfaceVersion::V2),
163      _ => Err(anyhow!("Unsupported plugin interface version {}", value)),
164    }
165  }
166}
167
168enum PluginClient {
169  V1(PactPluginClientV1<InterceptedService<Channel, PactPluginInterceptor>>),
170  V2(PactPluginClientV2<InterceptedService<Channel, PactPluginInterceptor>>),
171}
172
173impl PluginClient {
174  fn convert_message<T, U>(message: T) -> Result<U, Status>
175  where
176    T: Message,
177    U: Message + Default,
178  {
179    U::decode(message.encode_to_vec().as_slice()).map_err(|err| {
180      Status::internal(format!(
181        "Failed to convert between plugin interface message versions: {}",
182        err
183      ))
184    })
185  }
186
187  async fn init_plugin(
188    &mut self,
189    request: PluginInitRequest,
190  ) -> Result<PluginInitResponse, Status> {
191    match self {
192      PluginClient::V1(client) => client
193        .init_plugin(Request::new(InitPluginRequest {
194          implementation: request.implementation,
195          version: request.version,
196        }))
197        .await
198        .map(|response| PluginInitResponse {
199          catalogue: response.into_inner().catalogue,
200          plugin_capabilities: vec![],
201        }),
202      PluginClient::V2(client) => client
203        .init_plugin(Request::new(proto_v2::InitPluginRequest {
204          implementation: request.implementation,
205          version: request.version,
206          host_capabilities: request.host_capabilities,
207          plugin_instance_id: request.plugin_instance_id,
208        }))
209        .await
210        .and_then(|response| match response.into_inner().response {
211          Some(proto_v2::init_plugin_response::Response::Success(success)) => {
212            Ok(PluginInitResponse {
213              catalogue: success
214                .catalogue
215                .into_iter()
216                .map(Self::convert_message)
217                .collect::<Result<Vec<CatalogueEntry>, Status>>()?,
218              plugin_capabilities: success.plugin_capabilities,
219            })
220          }
221          Some(proto_v2::init_plugin_response::Response::Failure(failure)) => {
222            let mut error = failure.error;
223            if !failure.missing_host_capabilities.is_empty() {
224              error.push_str(" (missing host capabilities: ");
225              error.push_str(failure.missing_host_capabilities.join(", ").as_str());
226              error.push(')');
227            }
228            Err(Status::failed_precondition(error))
229          }
230          None => Err(Status::internal(
231            "Plugin returned an invalid V2 InitPlugin response",
232          )),
233        }),
234    }
235  }
236
237  async fn compare_contents(
238    &mut self,
239    request: CompareContentsRequest,
240  ) -> Result<CompareContentsResponse, Status> {
241    match self {
242      PluginClient::V1(client) => client
243        .compare_contents(Request::new(request))
244        .await
245        .map(|response| response.into_inner()),
246      PluginClient::V2(client) => {
247        let mut v2_req = Self::convert_message::<_, proto_v2::CompareContentsRequest>(request)?;
248        if v2_req.test_context.is_none() {
249          v2_req.test_context = crate::test_context::current_test_context();
250        }
251        client
252          .compare_contents(Request::new(v2_req))
253          .await
254          .and_then(|response| Self::convert_message(response.into_inner()))
255      }
256    }
257  }
258
259  async fn configure_interaction(
260    &mut self,
261    request: ConfigureInteractionRequest,
262  ) -> Result<ConfigureInteractionResponse, Status> {
263    match self {
264      PluginClient::V1(client) => client
265        .configure_interaction(Request::new(request))
266        .await
267        .map(|response| response.into_inner()),
268      PluginClient::V2(client) => {
269        let mut v2_req =
270          Self::convert_message::<_, proto_v2::ConfigureInteractionRequest>(request)?;
271        if v2_req.test_context.is_none() {
272          v2_req.test_context = crate::test_context::current_test_context();
273        }
274        client
275          .configure_interaction(Request::new(v2_req))
276          .await
277          .and_then(|response| Self::convert_message(response.into_inner()))
278      }
279    }
280  }
281
282  async fn generate_content(
283    &mut self,
284    request: GenerateContentRequest,
285  ) -> Result<GenerateContentResponse, Status> {
286    match self {
287      PluginClient::V1(client) => client
288        .generate_content(Request::new(request))
289        .await
290        .map(|response| response.into_inner()),
291      PluginClient::V2(client) => client
292        .generate_content(Request::new(Self::convert_message::<
293          _,
294          proto_v2::GenerateContentRequest,
295        >(request)?))
296        .await
297        .and_then(|response| Self::convert_message(response.into_inner())),
298    }
299  }
300
301  async fn start_mock_server(
302    &mut self,
303    request: StartMockServerRequest,
304  ) -> Result<StartMockServerResponse, Status> {
305    match self {
306      PluginClient::V1(client) => client
307        .start_mock_server(Request::new(request))
308        .await
309        .map(|response| response.into_inner()),
310      PluginClient::V2(client) => client
311        .start_mock_server(Request::new(Self::convert_message::<
312          _,
313          proto_v2::StartMockServerRequest,
314        >(request)?))
315        .await
316        .and_then(|response| Self::convert_message(response.into_inner())),
317    }
318  }
319
320  async fn shutdown_mock_server(
321    &mut self,
322    request: ShutdownMockServerRequest,
323  ) -> Result<ShutdownMockServerResponse, Status> {
324    match self {
325      PluginClient::V1(client) => client
326        .shutdown_mock_server(Request::new(request))
327        .await
328        .map(|response| response.into_inner()),
329      PluginClient::V2(client) => client
330        .shutdown_mock_server(Request::new(Self::convert_message::<
331          _,
332          proto_v2::MockServerRequest,
333        >(request)?))
334        .await
335        .and_then(|response| Self::convert_message::<_, ShutdownMockServerResponse>(response.into_inner())),
336    }
337  }
338
339  async fn start_mock_server_v2(
340    &mut self,
341    request: proto_v2::StartMockServerRequest,
342  ) -> Result<StartMockServerResponse, Status> {
343    match self {
344      PluginClient::V1(_) => Err(Status::unimplemented("V2 interface not supported on V1 plugin")),
345      PluginClient::V2(client) => client
346        .start_mock_server(Request::new(request))
347        .await
348        .and_then(|response| Self::convert_message(response.into_inner())),
349    }
350  }
351
352  async fn prepare_interaction_for_verification_v2(
353    &mut self,
354    request: proto_v2::VerificationPreparationRequest,
355  ) -> Result<VerificationPreparationResponse, Status> {
356    match self {
357      PluginClient::V1(_) => Err(Status::unimplemented("V2 interface not supported on V1 plugin")),
358      PluginClient::V2(client) => client
359        .prepare_interaction_for_verification(Request::new(request))
360        .await
361        .and_then(|response| Self::convert_message(response.into_inner())),
362    }
363  }
364
365  async fn verify_interaction_v2(
366    &mut self,
367    request: proto_v2::VerifyInteractionRequest,
368  ) -> Result<VerifyInteractionResponse, Status> {
369    match self {
370      PluginClient::V1(_) => Err(Status::unimplemented("V2 interface not supported on V1 plugin")),
371      PluginClient::V2(client) => client
372        .verify_interaction(Request::new(request))
373        .await
374        .and_then(|response| Self::convert_message(response.into_inner())),
375    }
376  }
377
378  async fn get_mock_server_results(
379    &mut self,
380    request: MockServerRequest,
381  ) -> Result<MockServerResults, Status> {
382    match self {
383      PluginClient::V1(client) => client
384        .get_mock_server_results(Request::new(request))
385        .await
386        .map(|response| response.into_inner()),
387      PluginClient::V2(client) => client
388        .get_mock_server_results(Request::new(Self::convert_message::<
389          _,
390          proto_v2::MockServerRequest,
391        >(request)?))
392        .await
393        .and_then(|response| Self::convert_message(response.into_inner())),
394    }
395  }
396
397  async fn prepare_interaction_for_verification(
398    &mut self,
399    request: VerificationPreparationRequest,
400  ) -> Result<VerificationPreparationResponse, Status> {
401    match self {
402      PluginClient::V1(client) => client
403        .prepare_interaction_for_verification(Request::new(request))
404        .await
405        .map(|response| response.into_inner()),
406      PluginClient::V2(client) => client
407        .prepare_interaction_for_verification(Request::new(Self::convert_message::<
408          _,
409          proto_v2::VerificationPreparationRequest,
410        >(request)?))
411        .await
412        .and_then(|response| Self::convert_message(response.into_inner())),
413    }
414  }
415
416  async fn verify_interaction(
417    &mut self,
418    request: VerifyInteractionRequest,
419  ) -> Result<VerifyInteractionResponse, Status> {
420    match self {
421      PluginClient::V1(client) => client
422        .verify_interaction(Request::new(request))
423        .await
424        .map(|response| response.into_inner()),
425      PluginClient::V2(client) => client
426        .verify_interaction(Request::new(Self::convert_message::<
427          _,
428          proto_v2::VerifyInteractionRequest,
429        >(request)?))
430        .await
431        .and_then(|response| Self::convert_message(response.into_inner())),
432    }
433  }
434
435  async fn update_catalogue(&mut self, request: Catalogue) -> Result<(), Status> {
436    match self {
437      PluginClient::V1(client) => client
438        .update_catalogue(Request::new(request))
439        .await
440        .map(|_| ()),
441      PluginClient::V2(client) => client
442        .update_catalogue(Request::new(
443          Self::convert_message::<_, proto_v2::Catalogue>(request)?,
444        ))
445        .await
446        .map(|_| ()),
447    }
448  }
449}
450
451/// Trait with remote-calling methods for a running plugin
452#[async_trait]
453pub trait PactPluginRpc {
454  /// Send an init request to the plugin process
455  async fn init_plugin(&mut self, request: PluginInitRequest)
456    -> anyhow::Result<PluginInitResponse>;
457
458  /// Send a compare contents request to the plugin process
459  async fn compare_contents(
460    &self,
461    request: CompareContentsRequest,
462  ) -> anyhow::Result<CompareContentsResponse>;
463
464  /// Send a configure contents request to the plugin process
465  async fn configure_interaction(
466    &self,
467    request: ConfigureInteractionRequest,
468  ) -> anyhow::Result<ConfigureInteractionResponse>;
469
470  /// Send a generate content request to the plugin
471  async fn generate_content(
472    &self,
473    request: GenerateContentRequest,
474  ) -> anyhow::Result<GenerateContentResponse>;
475
476  /// Start a mock server
477  async fn start_mock_server(
478    &self,
479    request: StartMockServerRequest,
480  ) -> anyhow::Result<StartMockServerResponse>;
481
482  /// Shutdown a running mock server
483  async fn shutdown_mock_server(
484    &self,
485    request: ShutdownMockServerRequest,
486  ) -> anyhow::Result<ShutdownMockServerResponse>;
487
488  /// Get the matching results from a running mock server
489  async fn get_mock_server_results(
490    &self,
491    request: MockServerRequest,
492  ) -> anyhow::Result<MockServerResults>;
493
494  /// Prepare an interaction for verification. This should return any data required to construct any request
495  /// so that it can be amended before the verification is run.
496  async fn prepare_interaction_for_verification(
497    &self,
498    request: VerificationPreparationRequest,
499  ) -> anyhow::Result<VerificationPreparationResponse>;
500
501  /// Execute the verification for the interaction.
502  async fn verify_interaction(
503    &self,
504    request: VerifyInteractionRequest,
505  ) -> anyhow::Result<VerifyInteractionResponse>;
506
507  /// Updates the catalogue. This will be sent when the core catalogue has been updated (probably by a plugin loading).
508  async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()>;
509
510  /// Start a mock server using V2 structured interaction data (no pact JSON).
511  async fn start_mock_server_v2(
512    &self,
513    request: proto_v2::StartMockServerRequest,
514  ) -> anyhow::Result<StartMockServerResponse> {
515    let _ = request;
516    Err(anyhow!("V2 interface not supported by this plugin"))
517  }
518
519  /// Prepare an interaction for verification using V2 structured interaction data.
520  async fn prepare_interaction_for_verification_v2(
521    &self,
522    request: proto_v2::VerificationPreparationRequest,
523  ) -> anyhow::Result<VerificationPreparationResponse> {
524    let _ = request;
525    Err(anyhow!("V2 interface not supported by this plugin"))
526  }
527
528  /// Execute the verification for the interaction using V2 structured interaction data.
529  async fn verify_interaction_v2(
530    &self,
531    request: proto_v2::VerifyInteractionRequest,
532  ) -> anyhow::Result<VerifyInteractionResponse> {
533    let _ = request;
534    Err(anyhow!("V2 interface not supported by this plugin"))
535  }
536}
537
538/// Running plugin details
539#[derive(Debug, Clone)]
540pub struct PactPlugin {
541  /// Manifest for this plugin
542  pub manifest: PactPluginManifest,
543
544  /// Interface version supported by the plugin
545  pub interface_version: PluginInterfaceVersion,
546
547  /// Running child process
548  pub child: Arc<ChildPluginProcess>,
549
550  /// Optional capabilities negotiated for this plugin instance
551  pub plugin_capabilities: Vec<String>,
552
553  /// UUID assigned by the driver at process start; used to correlate log output from this instance
554  pub instance_id: String,
555
556  /// Count of access to the plugin. If this is ever zero, the plugin process will be shutdown
557  access_count: Arc<AtomicUsize>,
558}
559
560#[async_trait]
561impl PactPluginRpc for PactPlugin {
562  /// Send an init request to the plugin process
563  async fn init_plugin(
564    &mut self,
565    request: PluginInitRequest,
566  ) -> anyhow::Result<PluginInitResponse> {
567    let mut client = self.get_plugin_client().await?;
568    client
569      .init_plugin(request)
570      .await
571      .map_err(anyhow::Error::from)
572  }
573
574  /// Send a compare contents request to the plugin process
575  async fn compare_contents(
576    &self,
577    request: CompareContentsRequest,
578  ) -> anyhow::Result<CompareContentsResponse> {
579    let mut client = self.get_plugin_client().await?;
580    client
581      .compare_contents(request)
582      .await
583      .map_err(anyhow::Error::from)
584  }
585
586  /// Send a configure contents request to the plugin process
587  async fn configure_interaction(
588    &self,
589    request: ConfigureInteractionRequest,
590  ) -> anyhow::Result<ConfigureInteractionResponse> {
591    let mut client = self.get_plugin_client().await?;
592    client
593      .configure_interaction(request)
594      .await
595      .map_err(anyhow::Error::from)
596  }
597
598  /// Send a generate content request to the plugin
599  async fn generate_content(
600    &self,
601    request: GenerateContentRequest,
602  ) -> anyhow::Result<GenerateContentResponse> {
603    let mut client = self.get_plugin_client().await?;
604    client
605      .generate_content(request)
606      .await
607      .map_err(anyhow::Error::from)
608  }
609
610  async fn start_mock_server(
611    &self,
612    request: StartMockServerRequest,
613  ) -> anyhow::Result<StartMockServerResponse> {
614    let mut client = self.get_plugin_client().await?;
615    client
616      .start_mock_server(request)
617      .await
618      .map_err(anyhow::Error::from)
619  }
620
621  async fn shutdown_mock_server(
622    &self,
623    request: ShutdownMockServerRequest,
624  ) -> anyhow::Result<ShutdownMockServerResponse> {
625    let mut client = self.get_plugin_client().await?;
626    client
627      .shutdown_mock_server(request)
628      .await
629      .map_err(anyhow::Error::from)
630  }
631
632  async fn get_mock_server_results(
633    &self,
634    request: MockServerRequest,
635  ) -> anyhow::Result<MockServerResults> {
636    let mut client = self.get_plugin_client().await?;
637    client
638      .get_mock_server_results(request)
639      .await
640      .map_err(anyhow::Error::from)
641  }
642
643  async fn prepare_interaction_for_verification(
644    &self,
645    request: VerificationPreparationRequest,
646  ) -> anyhow::Result<VerificationPreparationResponse> {
647    let mut client = self.get_plugin_client().await?;
648    client
649      .prepare_interaction_for_verification(request)
650      .await
651      .map_err(anyhow::Error::from)
652  }
653
654  async fn verify_interaction(
655    &self,
656    request: VerifyInteractionRequest,
657  ) -> anyhow::Result<VerifyInteractionResponse> {
658    let mut client = self.get_plugin_client().await?;
659    client
660      .verify_interaction(request)
661      .await
662      .map_err(anyhow::Error::from)
663  }
664
665  async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()> {
666    let mut client = self.get_plugin_client().await?;
667    client
668      .update_catalogue(request)
669      .await
670      .map_err(anyhow::Error::from)
671  }
672
673  async fn start_mock_server_v2(
674    &self,
675    request: proto_v2::StartMockServerRequest,
676  ) -> anyhow::Result<StartMockServerResponse> {
677    let mut client = self.get_plugin_client().await?;
678    client
679      .start_mock_server_v2(request)
680      .await
681      .map_err(anyhow::Error::from)
682  }
683
684  async fn prepare_interaction_for_verification_v2(
685    &self,
686    request: proto_v2::VerificationPreparationRequest,
687  ) -> anyhow::Result<VerificationPreparationResponse> {
688    let mut client = self.get_plugin_client().await?;
689    client
690      .prepare_interaction_for_verification_v2(request)
691      .await
692      .map_err(anyhow::Error::from)
693  }
694
695  async fn verify_interaction_v2(
696    &self,
697    request: proto_v2::VerifyInteractionRequest,
698  ) -> anyhow::Result<VerifyInteractionResponse> {
699    let mut client = self.get_plugin_client().await?;
700    client
701      .verify_interaction_v2(request)
702      .await
703      .map_err(anyhow::Error::from)
704  }
705}
706
707impl PactPlugin {
708  /// Create a new Plugin
709  pub fn new(manifest: &PactPluginManifest, child: ChildPluginProcess) -> anyhow::Result<Self> {
710    let instance_id = child.instance_id.clone();
711    Ok(PactPlugin {
712      manifest: manifest.clone(),
713      interface_version: PluginInterfaceVersion::try_from(manifest.plugin_interface_version)?,
714      instance_id,
715      child: Arc::new(child),
716      plugin_capabilities: vec![],
717      access_count: Arc::new(AtomicUsize::new(1)),
718    })
719  }
720
721  pub fn has_plugin_capability(&self, capability: &str) -> bool {
722    self.plugin_capabilities.iter().any(|value| value == capability)
723  }
724
725  /// Port the plugin is running on
726  pub fn port(&self) -> u16 {
727    self.child.port()
728  }
729
730  /// Kill the running plugin process
731  pub fn kill(&self) {
732    self.child.kill();
733  }
734
735  /// Update the access of the plugin
736  pub fn update_access(&mut self) {
737    let count = self.access_count.fetch_add(1, Ordering::SeqCst);
738    trace!(
739      "update_access: Plugin {}/{} access is now {}",
740      self.manifest.name,
741      self.manifest.version,
742      count + 1
743    );
744  }
745
746  /// Decrement and return the access count for the plugin
747  pub fn drop_access(&mut self) -> usize {
748    let check = self
749      .access_count
750      .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
751        if count > 0 { Some(count - 1) } else { None }
752      });
753    let count = if let Ok(v) = check {
754      if v > 0 { v - 1 } else { v }
755    } else {
756      0
757    };
758    trace!(
759      "drop_access: Plugin {}/{} access is now {}",
760      self.manifest.name, self.manifest.version, count
761    );
762    count
763  }
764
765  async fn connect_channel(&self) -> anyhow::Result<Channel> {
766    let port = self.child.port();
767    match Channel::from_shared(format!("http://[::1]:{}", port))?
768      .connect()
769      .await
770    {
771      Ok(channel) => Ok(channel),
772      Err(err) => {
773        debug!("IP6 connection failed, will try IP4 address - {err}");
774        Channel::from_shared(format!("http://127.0.0.1:{}", port))?
775          .connect()
776          .await
777          .map_err(|err| anyhow!(err))
778      }
779    }
780  }
781
782  async fn get_plugin_client(&self) -> anyhow::Result<PluginClient> {
783    let channel = self.connect_channel().await?;
784    let interceptor = PactPluginInterceptor::new(self.child.plugin_info.server_key.as_str())?;
785    match self.interface_version {
786      PluginInterfaceVersion::V1 => Ok(PluginClient::V1(PactPluginClientV1::with_interceptor(
787        channel,
788        interceptor,
789      ))),
790      PluginInterfaceVersion::V2 => Ok(PluginClient::V2(PactPluginClientV2::with_interceptor(
791        channel,
792        interceptor,
793      ))),
794    }
795  }
796}
797
798/// Interceptor to inject the server key as an authorisation header
799#[derive(Clone, Debug)]
800struct PactPluginInterceptor {
801  /// Server key to inject
802  server_key: MetadataValue<Ascii>,
803}
804
805impl PactPluginInterceptor {
806  fn new(server_key: &str) -> anyhow::Result<Self> {
807    let token = MetadataValue::try_from(server_key)?;
808    Ok(PactPluginInterceptor { server_key: token })
809  }
810}
811
812impl Interceptor for PactPluginInterceptor {
813  fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
814    request
815      .metadata_mut()
816      .insert("authorization", self.server_key.clone());
817    Ok(request)
818  }
819}
820
821/// Plugin configuration to add to the matching context for an interaction
822#[derive(Clone, Debug, PartialEq)]
823pub struct PluginInteractionConfig {
824  /// Global plugin config (Pact level)
825  pub pact_configuration: HashMap<String, Value>,
826  /// Interaction plugin config
827  pub interaction_configuration: HashMap<String, Value>,
828}
829
830#[cfg(test)]
831pub(crate) mod tests {
832  use std::collections::HashMap;
833  use std::sync::RwLock;
834
835  use async_trait::async_trait;
836  use tonic::Status;
837
838  use crate::plugin_models::{
839    PactPluginRpc, PluginClient, PluginInitRequest, PluginInitResponse,
840  };
841  use crate::proto::verification_preparation_response::Response;
842  use crate::proto::*;
843  use crate::proto_v2;
844
845  pub(crate) struct MockPlugin {
846    pub prepare_request: RwLock<VerificationPreparationRequest>,
847    pub verify_request: RwLock<VerifyInteractionRequest>,
848  }
849
850  impl Default for MockPlugin {
851    fn default() -> Self {
852      MockPlugin {
853        prepare_request: RwLock::new(VerificationPreparationRequest::default()),
854        verify_request: RwLock::new(VerifyInteractionRequest::default()),
855      }
856    }
857  }
858
859  #[test]
860  fn converts_between_v1_and_v2_messages() {
861    let request = PluginInitRequest {
862      implementation: "plugin-driver-rust".to_string(),
863      version: "1.0.0-beta.1".to_string(),
864      host_capabilities: vec!["interaction/request-response".to_string()],
865      plugin_instance_id: "test-instance-id".to_string(),
866    };
867
868    let converted_request = proto_v2::InitPluginRequest {
869      implementation: request.implementation,
870      version: request.version,
871      host_capabilities: request.host_capabilities,
872      plugin_instance_id: request.plugin_instance_id,
873    };
874    assert_eq!(converted_request.implementation, "plugin-driver-rust");
875    assert_eq!(converted_request.version, "1.0.0-beta.1");
876    assert_eq!(
877      converted_request.host_capabilities,
878      vec!["interaction/request-response"]
879    );
880
881    let response = proto_v2::InitPluginResponse {
882      response: Some(proto_v2::init_plugin_response::Response::Success(
883        proto_v2::InitPluginSuccess {
884          catalogue: vec![proto_v2::CatalogueEntry {
885            r#type: proto_v2::catalogue_entry::EntryType::ContentMatcher as i32,
886            key: "test".to_string(),
887            values: HashMap::new(),
888          }],
889          plugin_capabilities: vec!["plugin/verification".to_string()],
890        },
891      )),
892    };
893
894    let converted_response = match response.response.unwrap() {
895      proto_v2::init_plugin_response::Response::Success(success) => PluginInitResponse {
896        catalogue: success
897          .catalogue
898          .into_iter()
899          .map(PluginClient::convert_message)
900          .collect::<Result<Vec<CatalogueEntry>, Status>>()
901          .unwrap(),
902        plugin_capabilities: success.plugin_capabilities,
903      },
904      _ => unreachable!(),
905    };
906    assert_eq!(converted_response.catalogue.len(), 1);
907    assert_eq!(converted_response.catalogue[0].key, "test");
908    assert_eq!(
909      converted_response.catalogue[0].r#type,
910      catalogue_entry::EntryType::ContentMatcher as i32
911    );
912    assert_eq!(converted_response.plugin_capabilities, vec!["plugin/verification"]);
913  }
914
915  #[async_trait]
916  impl PactPluginRpc for MockPlugin {
917    async fn init_plugin(
918      &mut self,
919      _request: PluginInitRequest,
920    ) -> anyhow::Result<PluginInitResponse> {
921      unimplemented!()
922    }
923
924    async fn compare_contents(
925      &self,
926      _request: CompareContentsRequest,
927    ) -> anyhow::Result<CompareContentsResponse> {
928      unimplemented!()
929    }
930
931    async fn configure_interaction(
932      &self,
933      _request: ConfigureInteractionRequest,
934    ) -> anyhow::Result<ConfigureInteractionResponse> {
935      unimplemented!()
936    }
937
938    async fn generate_content(
939      &self,
940      _request: GenerateContentRequest,
941    ) -> anyhow::Result<GenerateContentResponse> {
942      unimplemented!()
943    }
944
945    async fn start_mock_server(
946      &self,
947      _request: StartMockServerRequest,
948    ) -> anyhow::Result<StartMockServerResponse> {
949      unimplemented!()
950    }
951
952    async fn shutdown_mock_server(
953      &self,
954      _request: ShutdownMockServerRequest,
955    ) -> anyhow::Result<ShutdownMockServerResponse> {
956      unimplemented!()
957    }
958
959    async fn get_mock_server_results(
960      &self,
961      _request: MockServerRequest,
962    ) -> anyhow::Result<MockServerResults> {
963      unimplemented!()
964    }
965
966    async fn prepare_interaction_for_verification(
967      &self,
968      request: VerificationPreparationRequest,
969    ) -> anyhow::Result<VerificationPreparationResponse> {
970      let mut w = self.prepare_request.write().unwrap();
971      *w = request;
972      let data = InteractionData {
973        body: None,
974        metadata: Default::default(),
975      };
976      Ok(VerificationPreparationResponse {
977        response: Some(Response::InteractionData(data)),
978      })
979    }
980
981    async fn verify_interaction(
982      &self,
983      request: VerifyInteractionRequest,
984    ) -> anyhow::Result<VerifyInteractionResponse> {
985      let mut w = self.verify_request.write().unwrap();
986      *w = request;
987      let result = VerificationResult {
988        success: false,
989        response_data: None,
990        mismatches: vec![],
991        output: vec![],
992      };
993      Ok(VerifyInteractionResponse {
994        response: Some(verify_interaction_response::Response::Result(result)),
995      })
996    }
997
998    async fn update_catalogue(&self, _request: Catalogue) -> anyhow::Result<()> {
999      unimplemented!()
1000    }
1001  }
1002}