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