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