1use serde::{Deserialize, Serialize};
11
12use crate::ir::DiffNode;
13use crate::traits::{ComparatorDescriptor, RendererDescriptor, TransformerDescriptor};
14use crate::types::{ArtifactDescriptor, CompareResult, ItemPair, TransformResult};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct PluginDescription {
21 pub sdk_version: String,
22 #[serde(default)]
23 pub comparators: Vec<ComparatorDescriptor>,
24 #[serde(default)]
25 pub transformers: Vec<TransformerDescriptor>,
26 #[serde(default)]
27 pub renderers: Vec<RendererDescriptor>,
28}
29
30#[derive(Debug, Serialize, Deserialize)]
33pub struct CompareRequest {
34 pub pair: ItemPair,
35 pub data_root: String,
36 pub workspace: String,
37}
38
39#[derive(Debug, Serialize, Deserialize)]
40#[serde(tag = "status")]
41pub enum CompareResponse {
42 #[serde(rename = "ok")]
43 Ok {
44 result: Box<CompareResult>,
45 #[serde(default, skip_serializing_if = "Vec::is_empty")]
46 artifacts: Vec<ArtifactDescriptor>,
47 },
48 #[serde(rename = "error")]
49 Error { message: String },
50}
51
52#[derive(Debug, Serialize, Deserialize)]
53pub struct ReopenRequest {
54 pub pair: ItemPair,
55 pub child_path: String,
56 pub data_root: String,
57 pub workspace: String,
58}
59
60#[derive(Debug, Serialize, Deserialize)]
61#[serde(tag = "status")]
62pub enum ReopenResponse {
63 #[serde(rename = "ok")]
64 Ok { pair: ItemPair },
65 #[serde(rename = "error")]
66 Error { message: String },
67}
68
69#[derive(Debug, Serialize, Deserialize)]
72pub struct TransformRequest {
73 pub node: DiffNode,
74 pub data_root: String,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub source_items: Option<ItemPair>,
77 #[serde(default, skip_serializing_if = "Vec::is_empty")]
78 pub artifacts: Vec<ArtifactDescriptor>,
79}
80
81#[derive(Debug, Serialize, Deserialize)]
83#[serde(tag = "status")]
84pub enum TransformResponse {
85 #[serde(rename = "unchanged")]
86 Unchanged,
87 #[serde(rename = "replace")]
88 Replace { node: Box<DiffNode> },
89 #[serde(rename = "replace_many")]
90 ReplaceMany { nodes: Vec<DiffNode> },
91 #[serde(rename = "remove")]
92 Remove,
93 #[serde(rename = "error")]
94 Error { message: String },
95}
96
97impl TransformResponse {
98 pub fn into_result(self) -> Result<TransformResult, String> {
99 match self {
100 Self::Unchanged => Ok(TransformResult::Unchanged),
101 Self::Replace { node } => Ok(TransformResult::Replace(node)),
102 Self::ReplaceMany { nodes } => Ok(TransformResult::ReplaceMany(nodes)),
103 Self::Remove => Ok(TransformResult::Remove),
104 Self::Error { message } => Err(message),
105 }
106 }
107}
108
109#[derive(Debug, Serialize, Deserialize)]
112pub struct RenderRequest {
113 pub changesets: Vec<crate::ir::Changeset>,
114 pub config: serde_json::Value,
115}
116
117#[derive(Debug, Serialize, Deserialize)]
118#[serde(tag = "status")]
119pub enum RenderResponse {
120 #[serde(rename = "ok")]
121 Ok { output: String },
122 #[serde(rename = "error")]
123 Error { message: String },
124}
125
126#[derive(Debug, Serialize, Deserialize)]
129pub struct ExtractRequest {
130 pub node: DiffNode,
131 pub aspect: String,
132 pub data_root: String,
133 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub source_items: Option<ItemPair>,
135 #[serde(default, skip_serializing_if = "Vec::is_empty")]
136 pub artifacts: Vec<ArtifactDescriptor>,
137}
138
139#[derive(Debug, Serialize, Deserialize)]
140#[serde(tag = "status")]
141pub enum ExtractResponse {
142 #[serde(rename = "text")]
143 Text { content: String },
144 #[serde(rename = "binary")]
145 Binary { content: Vec<u8> },
146 #[serde(rename = "none")]
147 None,
148 #[serde(rename = "error")]
149 Error { message: String },
150}
151
152#[macro_export]
177macro_rules! export_plugin {
178 (@comp_descs $($comp:ty),*) => {{
180 let mut descs = Vec::new();
181 $(
182 descs.push($crate::Comparator::descriptor(
183 &<$comp as ::std::default::Default>::default(),
184 ));
185 )*
186 descs
187 }};
188
189 (@trans_descs $($trans:ty),*) => {{
191 let mut descs = Vec::new();
192 $(
193 descs.push($crate::Transformer::descriptor(
194 &<$trans as ::std::default::Default>::default(),
195 ));
196 )*
197 descs
198 }};
199
200 (@out_descs $($out:ty),*) => {{
202 let mut descs = Vec::new();
203 $(
204 descs.push($crate::Renderer::descriptor(
205 &<$out as ::std::default::Default>::default(),
206 ));
207 )*
208 descs
209 }};
210
211 (@comparator_fns $($comp:ty),+) => {
213 #[no_mangle]
214 pub unsafe extern "C" fn _binoc_comparator_compare(
215 index: u32,
216 request: *const ::std::ffi::c_char,
217 ) -> *mut ::std::ffi::c_char {
218 let response = ::std::panic::catch_unwind(|| {
219 let request_str = ::std::ffi::CStr::from_ptr(request)
220 .to_str()
221 .expect("binoc SDK: valid UTF-8 request");
222 let req: $crate::plugin_abi::CompareRequest =
223 $crate::_reexport::serde_json::from_str(request_str)
224 .expect("binoc SDK: deserialize CompareRequest");
225 let data = $crate::LocalDataAccess::for_plugin(
226 ::std::path::PathBuf::from(&req.data_root),
227 ::std::path::PathBuf::from(&req.workspace),
228 );
229 let comparators: Vec<Box<dyn $crate::Comparator>> =
230 vec![$(Box::new(<$comp as ::std::default::Default>::default())),+];
231 let comp = &comparators[index as usize];
232 match $crate::Comparator::compare(comp.as_ref(), &req.pair, &data) {
233 Ok(result) => {
234 let artifacts = match &result {
235 $crate::CompareResult::Leaf(n) | $crate::CompareResult::Expand(n, _) => n.artifacts.clone(),
236 _ => Vec::new(),
237 };
238 $crate::plugin_abi::CompareResponse::Ok {
239 result: Box::new(result),
240 artifacts,
241 }
242 }
243 Err(e) => $crate::plugin_abi::CompareResponse::Error {
244 message: e.to_string(),
245 },
246 }
247 });
248 let response = match response {
249 Ok(r) => r,
250 Err(_) => $crate::plugin_abi::CompareResponse::Error {
251 message: "plugin panicked".to_string(),
252 },
253 };
254 let json = $crate::_reexport::serde_json::to_string(&response)
255 .expect("binoc SDK: serialize compare response");
256 ::std::ffi::CString::new(json)
257 .expect("binoc SDK: CString from JSON")
258 .into_raw()
259 }
260
261 #[no_mangle]
262 pub unsafe extern "C" fn _binoc_comparator_reopen(
263 index: u32,
264 request: *const ::std::ffi::c_char,
265 ) -> *mut ::std::ffi::c_char {
266 let response = ::std::panic::catch_unwind(|| {
267 let request_str = ::std::ffi::CStr::from_ptr(request)
268 .to_str()
269 .expect("binoc SDK: valid UTF-8 request");
270 let req: $crate::plugin_abi::ReopenRequest =
271 $crate::_reexport::serde_json::from_str(request_str)
272 .expect("binoc SDK: deserialize ReopenRequest");
273 let data = $crate::LocalDataAccess::for_plugin(
274 ::std::path::PathBuf::from(&req.data_root),
275 ::std::path::PathBuf::from(&req.workspace),
276 );
277 let comparators: Vec<Box<dyn $crate::Comparator>> =
278 vec![$(Box::new(<$comp as ::std::default::Default>::default())),+];
279 let comp = &comparators[index as usize];
280 match $crate::Comparator::reopen(comp.as_ref(), &req.pair, &req.child_path, &data) {
281 Ok(pair) => $crate::plugin_abi::ReopenResponse::Ok { pair },
282 Err(e) => $crate::plugin_abi::ReopenResponse::Error {
283 message: e.to_string(),
284 },
285 }
286 });
287 let response = match response {
288 Ok(r) => r,
289 Err(_) => $crate::plugin_abi::ReopenResponse::Error {
290 message: "plugin panicked".to_string(),
291 },
292 };
293 let json = $crate::_reexport::serde_json::to_string(&response)
294 .expect("binoc SDK: serialize reopen response");
295 ::std::ffi::CString::new(json)
296 .expect("binoc SDK: CString from JSON")
297 .into_raw()
298 }
299
300 #[no_mangle]
301 pub unsafe extern "C" fn _binoc_comparator_extract(
302 index: u32,
303 request: *const ::std::ffi::c_char,
304 ) -> *mut ::std::ffi::c_char {
305 let response = ::std::panic::catch_unwind(|| {
306 let request_str = ::std::ffi::CStr::from_ptr(request)
307 .to_str()
308 .expect("binoc SDK: valid UTF-8 request");
309 let req: $crate::plugin_abi::ExtractRequest =
310 $crate::_reexport::serde_json::from_str(request_str)
311 .expect("binoc SDK: deserialize ExtractRequest");
312 let data = $crate::LocalDataAccess::with_data_root(
313 ::std::path::PathBuf::from(&req.data_root),
314 );
315 let mut node = req.node;
316 node.source_items = req.source_items;
317 node.artifacts = req.artifacts;
318 let comparators: Vec<Box<dyn $crate::Comparator>> =
319 vec![$(Box::new(<$comp as ::std::default::Default>::default())),+];
320 let comp = &comparators[index as usize];
321 match $crate::Comparator::extract(comp.as_ref(), &node, &req.aspect, &data) {
322 Some($crate::ExtractResult::Text(t)) => {
323 $crate::plugin_abi::ExtractResponse::Text { content: t }
324 }
325 Some($crate::ExtractResult::Binary(b)) => {
326 $crate::plugin_abi::ExtractResponse::Binary { content: b }
327 }
328 None => $crate::plugin_abi::ExtractResponse::None,
329 }
330 });
331 let response = match response {
332 Ok(r) => r,
333 Err(_) => $crate::plugin_abi::ExtractResponse::Error {
334 message: "plugin panicked".to_string(),
335 },
336 };
337 let json = $crate::_reexport::serde_json::to_string(&response)
338 .expect("binoc SDK: serialize extract response");
339 ::std::ffi::CString::new(json)
340 .expect("binoc SDK: CString from JSON")
341 .into_raw()
342 }
343 };
344
345 (@transformer_fns $($trans:ty),+) => {
347 #[no_mangle]
348 pub unsafe extern "C" fn _binoc_transformer_transform(
349 index: u32,
350 request: *const ::std::ffi::c_char,
351 ) -> *mut ::std::ffi::c_char {
352 let response = ::std::panic::catch_unwind(|| {
353 let request_str = ::std::ffi::CStr::from_ptr(request)
354 .to_str()
355 .expect("binoc SDK: valid UTF-8 request");
356 let req: $crate::plugin_abi::TransformRequest =
357 $crate::_reexport::serde_json::from_str(request_str)
358 .expect("binoc SDK: deserialize TransformRequest");
359 let data = $crate::LocalDataAccess::with_data_root(
360 ::std::path::PathBuf::from(&req.data_root),
361 );
362 let mut node = req.node;
363 node.source_items = req.source_items;
364 node.artifacts = req.artifacts;
365 let transformers: Vec<Box<dyn $crate::Transformer>> =
366 vec![$(Box::new(<$trans as ::std::default::Default>::default())),+];
367 let trans = &transformers[index as usize];
368 match $crate::Transformer::transform(trans.as_ref(), node, &data) {
369 $crate::TransformResult::Unchanged => {
370 $crate::plugin_abi::TransformResponse::Unchanged
371 }
372 $crate::TransformResult::Replace(node) => {
373 $crate::plugin_abi::TransformResponse::Replace { node }
374 }
375 $crate::TransformResult::ReplaceMany(nodes) => {
376 $crate::plugin_abi::TransformResponse::ReplaceMany { nodes }
377 }
378 $crate::TransformResult::Remove => {
379 $crate::plugin_abi::TransformResponse::Remove
380 }
381 _ => $crate::plugin_abi::TransformResponse::Unchanged,
382 }
383 });
384 let response = match response {
385 Ok(r) => r,
386 Err(_) => $crate::plugin_abi::TransformResponse::Error {
387 message: "plugin panicked".to_string(),
388 },
389 };
390 let json = $crate::_reexport::serde_json::to_string(&response)
391 .expect("binoc SDK: serialize transform response");
392 ::std::ffi::CString::new(json)
393 .expect("binoc SDK: CString from JSON")
394 .into_raw()
395 }
396
397 #[no_mangle]
398 pub unsafe extern "C" fn _binoc_transformer_extract(
399 index: u32,
400 request: *const ::std::ffi::c_char,
401 ) -> *mut ::std::ffi::c_char {
402 let response = ::std::panic::catch_unwind(|| {
403 let request_str = ::std::ffi::CStr::from_ptr(request)
404 .to_str()
405 .expect("binoc SDK: valid UTF-8 request");
406 let req: $crate::plugin_abi::ExtractRequest =
407 $crate::_reexport::serde_json::from_str(request_str)
408 .expect("binoc SDK: deserialize ExtractRequest");
409 let data = $crate::LocalDataAccess::with_data_root(
410 ::std::path::PathBuf::from(&req.data_root),
411 );
412 let mut node = req.node;
413 node.source_items = req.source_items;
414 node.artifacts = req.artifacts;
415 let transformers: Vec<Box<dyn $crate::Transformer>> =
416 vec![$(Box::new(<$trans as ::std::default::Default>::default())),+];
417 let trans = &transformers[index as usize];
418 match $crate::Transformer::extract(trans.as_ref(), &node, &req.aspect, &data) {
419 Some($crate::ExtractResult::Text(t)) => {
420 $crate::plugin_abi::ExtractResponse::Text { content: t }
421 }
422 Some($crate::ExtractResult::Binary(b)) => {
423 $crate::plugin_abi::ExtractResponse::Binary { content: b }
424 }
425 None => $crate::plugin_abi::ExtractResponse::None,
426 }
427 });
428 let response = match response {
429 Ok(r) => r,
430 Err(_) => $crate::plugin_abi::ExtractResponse::Error {
431 message: "plugin panicked".to_string(),
432 },
433 };
434 let json = $crate::_reexport::serde_json::to_string(&response)
435 .expect("binoc SDK: serialize extract response");
436 ::std::ffi::CString::new(json)
437 .expect("binoc SDK: CString from JSON")
438 .into_raw()
439 }
440 };
441
442 (@renderer_fns $($out:ty),+) => {
444 #[no_mangle]
445 pub unsafe extern "C" fn _binoc_renderer_render(
446 index: u32,
447 request: *const ::std::ffi::c_char,
448 ) -> *mut ::std::ffi::c_char {
449 let response = ::std::panic::catch_unwind(|| {
450 let request_str = ::std::ffi::CStr::from_ptr(request)
451 .to_str()
452 .expect("binoc SDK: valid UTF-8 request");
453 let req: $crate::plugin_abi::RenderRequest =
454 $crate::_reexport::serde_json::from_str(request_str)
455 .expect("binoc SDK: deserialize RenderRequest");
456 let renderers: Vec<Box<dyn $crate::Renderer>> =
457 vec![$(Box::new(<$out as ::std::default::Default>::default())),+];
458 let out = &renderers[index as usize];
459 match $crate::Renderer::render(out.as_ref(), &req.changesets, &req.config) {
460 Ok(output) => $crate::plugin_abi::RenderResponse::Ok { output },
461 Err(e) => $crate::plugin_abi::RenderResponse::Error {
462 message: e.to_string(),
463 },
464 }
465 });
466 let response = match response {
467 Ok(r) => r,
468 Err(_) => $crate::plugin_abi::RenderResponse::Error {
469 message: "plugin panicked".to_string(),
470 },
471 };
472 let json = $crate::_reexport::serde_json::to_string(&response)
473 .expect("binoc SDK: serialize render response");
474 ::std::ffi::CString::new(json)
475 .expect("binoc SDK: CString from JSON")
476 .into_raw()
477 }
478 };
479
480 (
482 module: $module_name:ident,
483 comparators: [$($comp:ty),+ $(,)?] $(,)?
484 ) => {
485 #[no_mangle]
486 pub extern "C" fn _binoc_plugin_describe() -> *mut ::std::ffi::c_char {
487 let desc = $crate::plugin_abi::PluginDescription {
488 sdk_version: $crate::SDK_VERSION.to_string(),
489 comparators: $crate::export_plugin!(@comp_descs $($comp),+),
490 transformers: vec![],
491 renderers: vec![],
492 };
493 let json = $crate::_reexport::serde_json::to_string(&desc)
494 .expect("binoc SDK: serialize plugin description");
495 ::std::ffi::CString::new(json)
496 .expect("binoc SDK: CString from JSON")
497 .into_raw()
498 }
499
500 #[no_mangle]
501 pub unsafe extern "C" fn _binoc_free_string(s: *mut ::std::ffi::c_char) {
502 if !s.is_null() {
503 drop(::std::ffi::CString::from_raw(s));
504 }
505 }
506
507 $crate::export_plugin!(@comparator_fns $($comp),+);
508
509 #[cfg(feature = "python")]
510 #[::pyo3::pymodule]
511 fn $module_name(_m: &::pyo3::Bound<'_, ::pyo3::types::PyModule>) -> ::pyo3::PyResult<()> {
512 Ok(())
513 }
514 };
515
516 (
518 module: $module_name:ident,
519 transformers: [$($trans:ty),+ $(,)?] $(,)?
520 ) => {
521 #[no_mangle]
522 pub extern "C" fn _binoc_plugin_describe() -> *mut ::std::ffi::c_char {
523 let desc = $crate::plugin_abi::PluginDescription {
524 sdk_version: $crate::SDK_VERSION.to_string(),
525 comparators: vec![],
526 transformers: $crate::export_plugin!(@trans_descs $($trans),+),
527 renderers: vec![],
528 };
529 let json = $crate::_reexport::serde_json::to_string(&desc)
530 .expect("binoc SDK: serialize plugin description");
531 ::std::ffi::CString::new(json)
532 .expect("binoc SDK: CString from JSON")
533 .into_raw()
534 }
535
536 #[no_mangle]
537 pub unsafe extern "C" fn _binoc_free_string(s: *mut ::std::ffi::c_char) {
538 if !s.is_null() {
539 drop(::std::ffi::CString::from_raw(s));
540 }
541 }
542
543 $crate::export_plugin!(@transformer_fns $($trans),+);
544
545 #[cfg(feature = "python")]
546 #[::pyo3::pymodule]
547 fn $module_name(_m: &::pyo3::Bound<'_, ::pyo3::types::PyModule>) -> ::pyo3::PyResult<()> {
548 Ok(())
549 }
550 };
551
552 (
554 module: $module_name:ident,
555 renderers: [$($out:ty),+ $(,)?] $(,)?
556 ) => {
557 #[no_mangle]
558 pub extern "C" fn _binoc_plugin_describe() -> *mut ::std::ffi::c_char {
559 let desc = $crate::plugin_abi::PluginDescription {
560 sdk_version: $crate::SDK_VERSION.to_string(),
561 comparators: vec![],
562 transformers: vec![],
563 renderers: $crate::export_plugin!(@out_descs $($out),+),
564 };
565 let json = $crate::_reexport::serde_json::to_string(&desc)
566 .expect("binoc SDK: serialize plugin description");
567 ::std::ffi::CString::new(json)
568 .expect("binoc SDK: CString from JSON")
569 .into_raw()
570 }
571
572 #[no_mangle]
573 pub unsafe extern "C" fn _binoc_free_string(s: *mut ::std::ffi::c_char) {
574 if !s.is_null() {
575 drop(::std::ffi::CString::from_raw(s));
576 }
577 }
578
579 $crate::export_plugin!(@renderer_fns $($out),+);
580
581 #[cfg(feature = "python")]
582 #[::pyo3::pymodule]
583 fn $module_name(_m: &::pyo3::Bound<'_, ::pyo3::types::PyModule>) -> ::pyo3::PyResult<()> {
584 Ok(())
585 }
586 };
587
588 (
590 module: $module_name:ident,
591 comparators: [$($comp:ty),+ $(,)?],
592 transformers: [$($trans:ty),+ $(,)?] $(,)?
593 ) => {
594 #[no_mangle]
595 pub extern "C" fn _binoc_plugin_describe() -> *mut ::std::ffi::c_char {
596 let desc = $crate::plugin_abi::PluginDescription {
597 sdk_version: $crate::SDK_VERSION.to_string(),
598 comparators: $crate::export_plugin!(@comp_descs $($comp),+),
599 transformers: $crate::export_plugin!(@trans_descs $($trans),+),
600 renderers: vec![],
601 };
602 let json = $crate::_reexport::serde_json::to_string(&desc)
603 .expect("binoc SDK: serialize plugin description");
604 ::std::ffi::CString::new(json)
605 .expect("binoc SDK: CString from JSON")
606 .into_raw()
607 }
608
609 #[no_mangle]
610 pub unsafe extern "C" fn _binoc_free_string(s: *mut ::std::ffi::c_char) {
611 if !s.is_null() {
612 drop(::std::ffi::CString::from_raw(s));
613 }
614 }
615
616 $crate::export_plugin!(@comparator_fns $($comp),+);
617 $crate::export_plugin!(@transformer_fns $($trans),+);
618
619 #[cfg(feature = "python")]
620 #[::pyo3::pymodule]
621 fn $module_name(_m: &::pyo3::Bound<'_, ::pyo3::types::PyModule>) -> ::pyo3::PyResult<()> {
622 Ok(())
623 }
624 };
625}