Skip to main content

pact_plugin_driver/
plugin_models.rs

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