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 serde::{Deserialize, Serialize};
11use serde_json::Value;
12use tonic::{Request, Status};
13use tonic::codegen::InterceptedService;
14use tonic::metadata::{Ascii, MetadataValue};
15use tonic::service::Interceptor;
16use tonic::transport::Channel;
17use tracing::{debug, trace};
18
19use crate::child_process::ChildPluginProcess;
20use crate::proto::*;
21use crate::proto::pact_plugin_client::PactPluginClient;
22
23#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
25pub enum PluginDependencyType {
26 OSPackage,
28 Plugin,
30 Library,
32 Executable
34}
35
36impl Default for PluginDependencyType {
37 fn default() -> Self {
38 PluginDependencyType::Plugin
39 }
40}
41
42#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)]
44#[serde(rename_all = "camelCase")]
45pub struct PluginDependency {
46 pub name: String,
48 pub version: Option<String>,
50 #[serde(default)]
52 pub dependency_type: PluginDependencyType
53}
54
55impl Display for PluginDependency {
56 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
57 if let Some(version) = &self.version {
58 write!(f, "{}:{}", self.name, version)
59 } else {
60 write!(f, "{}:*", self.name)
61 }
62 }
63}
64
65#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
67#[serde(rename_all = "camelCase")]
68pub struct PactPluginManifest {
69 #[serde(skip)]
71 pub plugin_dir: String,
72
73 pub plugin_interface_version: u8,
75
76 pub name: String,
78
79 pub version: String,
81
82 pub executable_type: String,
84
85 pub minimum_required_version: Option<String>,
87
88 pub entry_point: String,
90
91 #[serde(default)]
93 pub entry_points: HashMap<String, String>,
94
95 pub args: Option<Vec<String>>,
97
98 pub dependencies: Option<Vec<PluginDependency>>,
100
101 #[serde(default)]
103 pub plugin_config: HashMap<String, Value>
104}
105
106impl PactPluginManifest {
107 pub fn as_dependency(&self) -> PluginDependency {
108 PluginDependency {
109 name: self.name.clone(),
110 version: Some(self.version.clone()),
111 dependency_type: PluginDependencyType::Plugin
112 }
113 }
114}
115
116impl Default for PactPluginManifest {
117 fn default() -> Self {
118 PactPluginManifest {
119 plugin_dir: "".to_string(),
120 plugin_interface_version: 1,
121 name: "".to_string(),
122 version: "".to_string(),
123 executable_type: "".to_string(),
124 minimum_required_version: None,
125 entry_point: "".to_string(),
126 entry_points: Default::default(),
127 args: None,
128 dependencies: None,
129 plugin_config: Default::default()
130 }
131 }
132}
133
134#[async_trait]
136pub trait PactPluginRpc {
137 async fn init_plugin(&mut self, request: InitPluginRequest) -> anyhow::Result<InitPluginResponse>;
139
140 async fn compare_contents(&self, request: CompareContentsRequest) -> anyhow::Result<CompareContentsResponse>;
142
143 async fn configure_interaction(&self, request: ConfigureInteractionRequest) -> anyhow::Result<ConfigureInteractionResponse>;
145
146 async fn generate_content(&self, request: GenerateContentRequest) -> anyhow::Result<GenerateContentResponse>;
148
149 async fn start_mock_server(&self, request: StartMockServerRequest) -> anyhow::Result<StartMockServerResponse>;
151
152 async fn shutdown_mock_server(&self, request: ShutdownMockServerRequest) -> anyhow::Result<ShutdownMockServerResponse>;
154
155 async fn get_mock_server_results(&self, request: MockServerRequest) -> anyhow::Result<MockServerResults>;
157
158 async fn prepare_interaction_for_verification(&self, request: VerificationPreparationRequest) -> anyhow::Result<VerificationPreparationResponse>;
161
162 async fn verify_interaction(&self, request: VerifyInteractionRequest) -> anyhow::Result<VerifyInteractionResponse>;
164
165 async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()>;
167}
168
169#[derive(Debug, Clone)]
171pub struct PactPlugin {
172 pub manifest: PactPluginManifest,
174
175 pub child: Arc<ChildPluginProcess>,
177
178 access_count: Arc<AtomicUsize>
180}
181
182#[async_trait]
183impl PactPluginRpc for PactPlugin {
184 async fn init_plugin(&mut self, request: InitPluginRequest) -> anyhow::Result<InitPluginResponse> {
186 let mut client = self.get_plugin_client().await?;
187 let response = client.init_plugin(Request::new(request)).await?;
188 Ok(response.get_ref().clone())
189 }
190
191 async fn compare_contents(&self, request: CompareContentsRequest) -> anyhow::Result<CompareContentsResponse> {
193 let mut client = self.get_plugin_client().await?;
194 let response = client.compare_contents(tonic::Request::new(request)).await?;
195 Ok(response.get_ref().clone())
196 }
197
198 async fn configure_interaction(&self, request: ConfigureInteractionRequest) -> anyhow::Result<ConfigureInteractionResponse> {
200 let mut client = self.get_plugin_client().await?;
201 let response = client.configure_interaction(tonic::Request::new(request)).await?;
202 Ok(response.get_ref().clone())
203 }
204
205 async fn generate_content(&self, request: GenerateContentRequest) -> anyhow::Result<GenerateContentResponse> {
207 let mut client = self.get_plugin_client().await?;
208 let response = client.generate_content(tonic::Request::new(request)).await?;
209 Ok(response.get_ref().clone())
210 }
211
212 async fn start_mock_server(&self, request: StartMockServerRequest) -> anyhow::Result<StartMockServerResponse> {
213 let mut client = self.get_plugin_client().await?;
214 let response = client.start_mock_server(tonic::Request::new(request)).await?;
215 Ok(response.get_ref().clone())
216 }
217
218 async fn shutdown_mock_server(&self, request: ShutdownMockServerRequest) -> anyhow::Result<ShutdownMockServerResponse> {
219 let mut client = self.get_plugin_client().await?;
220 let response = client.shutdown_mock_server(tonic::Request::new(request)).await?;
221 Ok(response.get_ref().clone())
222 }
223
224 async fn get_mock_server_results(&self, request: MockServerRequest) -> anyhow::Result<MockServerResults> {
225 let mut client = self.get_plugin_client().await?;
226 let response = client.get_mock_server_results(tonic::Request::new(request)).await?;
227 Ok(response.get_ref().clone())
228 }
229
230 async fn prepare_interaction_for_verification(&self, request: VerificationPreparationRequest) -> anyhow::Result<VerificationPreparationResponse> {
231 let mut client = self.get_plugin_client().await?;
232 let response = client.prepare_interaction_for_verification(tonic::Request::new(request)).await?;
233 Ok(response.get_ref().clone())
234 }
235
236 async fn verify_interaction(&self, request: VerifyInteractionRequest) -> anyhow::Result<VerifyInteractionResponse> {
237 let mut client = self.get_plugin_client().await?;
238 let response = client.verify_interaction(tonic::Request::new(request)).await?;
239 Ok(response.get_ref().clone())
240 }
241
242 async fn update_catalogue(&self, request: Catalogue) -> anyhow::Result<()> {
243 let mut client = self.get_plugin_client().await?;
244 client.update_catalogue(tonic::Request::new(request)).await?;
245 Ok(())
246 }
247}
248
249impl PactPlugin {
250 pub fn new(manifest: &PactPluginManifest, child: ChildPluginProcess) -> Self {
252 PactPlugin {
253 manifest: manifest.clone(),
254 child: Arc::new(child),
255 access_count: Arc::new(AtomicUsize::new(1))
256 }
257 }
258
259 pub fn port(&self) -> u16 {
261 self.child.port()
262 }
263
264 pub fn kill(&self) {
266 self.child.kill();
267 }
268
269 pub fn update_access(&mut self) {
271 let count = self.access_count.fetch_add(1, Ordering::SeqCst);
272 trace!("update_access: Plugin {}/{} access is now {}", self.manifest.name,
273 self.manifest.version, count + 1);
274 }
275
276 pub fn drop_access(&mut self) -> usize {
278 let check = self.access_count.fetch_update(Ordering::SeqCst,
279 Ordering::SeqCst, |count| {
280 if count > 0 {
281 Some(count - 1)
282 } else {
283 None
284 }
285 });
286 let count = if let Ok(v) = check {
287 if v > 0 { v - 1 } else { v }
288 } else {
289 0
290 };
291 trace!("drop_access: Plugin {}/{} access is now {}", self.manifest.name, self.manifest.version,
292 count);
293 count
294 }
295
296 async fn connect_channel(&self) -> anyhow::Result<Channel> {
297 let port = self.child.port();
298 match Channel::from_shared(format!("http://[::1]:{}", port))?.connect().await {
299 Ok(channel) => Ok(channel),
300 Err(err) => {
301 debug!("IP6 connection failed, will try IP4 address - {err}");
302 Channel::from_shared(format!("http://127.0.0.1:{}", port))?.connect().await
303 .map_err(|err| anyhow!(err))
304 }
305 }
306 }
307
308 async fn get_plugin_client(&self) -> anyhow::Result<PactPluginClient<InterceptedService<Channel, PactPluginInterceptor>>> {
309 let channel = self.connect_channel().await?;
310 let interceptor = PactPluginInterceptor::new(self.child.plugin_info.server_key.as_str())?;
311 Ok(PactPluginClient::with_interceptor(channel, interceptor))
312 }
313}
314
315#[derive(Clone, Debug)]
317struct PactPluginInterceptor {
318 server_key: MetadataValue<Ascii>
320}
321
322impl PactPluginInterceptor {
323 fn new(server_key: &str) -> anyhow::Result<Self> {
324 let token = MetadataValue::try_from(server_key)?;
325 Ok(PactPluginInterceptor {
326 server_key: token
327 })
328 }
329}
330
331impl Interceptor for PactPluginInterceptor {
332 fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
333 request.metadata_mut().insert("authorization", self.server_key.clone());
334 Ok(request)
335 }
336}
337
338#[derive(Clone, Debug, PartialEq)]
340pub struct PluginInteractionConfig {
341 pub pact_configuration: HashMap<String, Value>,
343 pub interaction_configuration: HashMap<String, Value>
345}
346
347#[cfg(test)]
348pub(crate) mod tests {
349 use std::sync::RwLock;
350
351 use async_trait::async_trait;
352
353 use crate::plugin_models::PactPluginRpc;
354 use crate::proto::*;
355 use crate::proto::verification_preparation_response::Response;
356
357 pub(crate) struct MockPlugin {
358 pub prepare_request: RwLock<VerificationPreparationRequest>,
359 pub verify_request: RwLock<VerifyInteractionRequest>
360 }
361
362 impl Default for MockPlugin {
363 fn default() -> Self {
364 MockPlugin {
365 prepare_request: RwLock::new(VerificationPreparationRequest::default()),
366 verify_request: RwLock::new(VerifyInteractionRequest::default())
367 }
368 }
369 }
370
371 #[async_trait]
372 impl PactPluginRpc for MockPlugin {
373 async fn init_plugin(&mut self, _request: InitPluginRequest) -> anyhow::Result<InitPluginResponse> {
374 unimplemented!()
375 }
376
377 async fn compare_contents(&self, _request: CompareContentsRequest) -> anyhow::Result<CompareContentsResponse> {
378 unimplemented!()
379 }
380
381 async fn configure_interaction(&self, _request: ConfigureInteractionRequest) -> anyhow::Result<ConfigureInteractionResponse> {
382 unimplemented!()
383 }
384
385 async fn generate_content(&self, _request: GenerateContentRequest) -> anyhow::Result<GenerateContentResponse> {
386 unimplemented!()
387 }
388
389 async fn start_mock_server(&self, _request: StartMockServerRequest) -> anyhow::Result<StartMockServerResponse> {
390 unimplemented!()
391 }
392
393 async fn shutdown_mock_server(&self, _request: ShutdownMockServerRequest) -> anyhow::Result<ShutdownMockServerResponse> {
394 unimplemented!()
395 }
396
397 async fn get_mock_server_results(&self, _request: MockServerRequest) -> anyhow::Result<MockServerResults> {
398 unimplemented!()
399 }
400
401 async fn prepare_interaction_for_verification(&self, request: VerificationPreparationRequest) -> anyhow::Result<VerificationPreparationResponse> {
402 let mut w = self.prepare_request.write().unwrap();
403 *w = request;
404 let data = InteractionData {
405 body: None,
406 metadata: Default::default()
407 };
408 Ok(VerificationPreparationResponse {
409 response: Some(Response::InteractionData(data))
410 })
411 }
412
413 async fn verify_interaction(&self, request: VerifyInteractionRequest) -> anyhow::Result<VerifyInteractionResponse> {
414 let mut w = self.verify_request.write().unwrap();
415 *w = request;
416 let result = VerificationResult {
417 success: false,
418 response_data: None,
419 mismatches: vec![],
420 output: vec![]
421 };
422 Ok(VerifyInteractionResponse {
423 response: Some(verify_interaction_response::Response::Result(result))
424 })
425 }
426
427 async fn update_catalogue(&self, _request: Catalogue) -> anyhow::Result<()> {
428 unimplemented!()
429 }
430 }
431}