1use 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#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
27pub enum PluginDependencyType {
28 OSPackage,
30 Plugin,
32 Library,
34 Executable,
36}
37
38impl Default for PluginDependencyType {
39 fn default() -> Self {
40 PluginDependencyType::Plugin
41 }
42}
43
44#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
46#[serde(rename_all = "camelCase")]
47pub struct PluginDependency {
48 pub name: String,
50 pub version: Option<String>,
52 #[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#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
69#[serde(rename_all = "camelCase")]
70pub struct PactPluginManifest {
71 #[serde(skip)]
73 pub plugin_dir: String,
74
75 pub plugin_interface_version: u8,
77
78 pub name: String,
80
81 pub version: String,
83
84 pub executable_type: String,
86
87 pub minimum_required_version: Option<String>,
89
90 pub entry_point: String,
92
93 #[serde(default)]
95 pub entry_points: HashMap<String, String>,
96
97 pub args: Option<Vec<String>>,
99
100 pub dependencies: Option<Vec<PluginDependency>>,
102
103 #[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#[async_trait]
446pub trait PactPluginRpc {
447 async fn init_plugin(&mut self, request: PluginInitRequest)
449 -> anyhow::Result<PluginInitResponse>;
450
451 async fn compare_contents(
453 &self,
454 request: CompareContentsRequest,
455 ) -> anyhow::Result<CompareContentsResponse>;
456
457 async fn configure_interaction(
459 &self,
460 request: ConfigureInteractionRequest,
461 ) -> anyhow::Result<ConfigureInteractionResponse>;
462
463 async fn generate_content(
465 &self,
466 request: GenerateContentRequest,
467 ) -> anyhow::Result<GenerateContentResponse>;
468
469 async fn start_mock_server(
471 &self,
472 request: StartMockServerRequest,
473 ) -> anyhow::Result<StartMockServerResponse>;
474
475 async fn shutdown_mock_server(
477 &self,
478 request: ShutdownMockServerRequest,
479 ) -> anyhow::Result<ShutdownMockServerResponse>;
480
481 async fn get_mock_server_results(
483 &self,
484 request: MockServerRequest,
485 ) -> anyhow::Result<MockServerResults>;
486
487 async fn prepare_interaction_for_verification(
490 &self,
491 request: VerificationPreparationRequest,
492 ) -> anyhow::Result<VerificationPreparationResponse>;
493
494 async fn verify_interaction(
496 &self,
497 request: VerifyInteractionRequest,
498 ) -> anyhow::Result<VerifyInteractionResponse>;
499
500 async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()>;
502
503 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 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 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#[derive(Debug, Clone)]
533pub struct PactPlugin {
534 pub manifest: PactPluginManifest,
536
537 pub interface_version: PluginInterfaceVersion,
539
540 pub child: Arc<ChildPluginProcess>,
542
543 pub plugin_capabilities: Vec<String>,
545
546 pub instance_id: String,
548
549 access_count: Arc<AtomicUsize>,
551}
552
553#[async_trait]
554impl PactPluginRpc for PactPlugin {
555 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 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 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 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 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 pub fn port(&self) -> u16 {
720 self.child.port()
721 }
722
723 pub fn kill(&self) {
725 self.child.kill();
726 }
727
728 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 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#[derive(Clone, Debug)]
793struct PactPluginInterceptor {
794 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#[derive(Clone, Debug, PartialEq)]
816pub struct PluginInteractionConfig {
817 pub pact_configuration: HashMap<String, Value>,
819 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}