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