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