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