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}
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#[async_trait]
444pub trait PactPluginRpc {
445 async fn init_plugin(&mut self, request: PluginInitRequest)
447 -> anyhow::Result<PluginInitResponse>;
448
449 async fn compare_contents(
451 &self,
452 request: CompareContentsRequest,
453 ) -> anyhow::Result<CompareContentsResponse>;
454
455 async fn configure_interaction(
457 &self,
458 request: ConfigureInteractionRequest,
459 ) -> anyhow::Result<ConfigureInteractionResponse>;
460
461 async fn generate_content(
463 &self,
464 request: GenerateContentRequest,
465 ) -> anyhow::Result<GenerateContentResponse>;
466
467 async fn start_mock_server(
469 &self,
470 request: StartMockServerRequest,
471 ) -> anyhow::Result<StartMockServerResponse>;
472
473 async fn shutdown_mock_server(
475 &self,
476 request: ShutdownMockServerRequest,
477 ) -> anyhow::Result<ShutdownMockServerResponse>;
478
479 async fn get_mock_server_results(
481 &self,
482 request: MockServerRequest,
483 ) -> anyhow::Result<MockServerResults>;
484
485 async fn prepare_interaction_for_verification(
488 &self,
489 request: VerificationPreparationRequest,
490 ) -> anyhow::Result<VerificationPreparationResponse>;
491
492 async fn verify_interaction(
494 &self,
495 request: VerifyInteractionRequest,
496 ) -> anyhow::Result<VerifyInteractionResponse>;
497
498 async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()>;
500
501 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 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 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#[derive(Debug, Clone)]
531pub struct PactPlugin {
532 pub manifest: PactPluginManifest,
534
535 pub interface_version: PluginInterfaceVersion,
537
538 pub child: Arc<ChildPluginProcess>,
540
541 pub plugin_capabilities: Vec<String>,
543
544 access_count: Arc<AtomicUsize>,
546}
547
548#[async_trait]
549impl PactPluginRpc for PactPlugin {
550 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 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 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 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 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 pub fn port(&self) -> u16 {
713 self.child.port()
714 }
715
716 pub fn kill(&self) {
718 self.child.kill();
719 }
720
721 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 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#[derive(Clone, Debug)]
786struct PactPluginInterceptor {
787 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#[derive(Clone, Debug, PartialEq)]
809pub struct PluginInteractionConfig {
810 pub pact_configuration: HashMap<String, Value>,
812 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}