1use std::ffi::{CStr, CString};
2use std::os::raw::{c_char, c_int, c_void};
3use std::ptr;
4
5use crate::config::{
6 ruleset_from_file, ruleset_from_file_with_processors as load_file_with_processors,
7 ruleset_from_str, ruleset_from_str_with_processors as load_str_with_processors,
8};
9use crate::render::{Processor, ProcessorRegistry, RenderContext, RenderOptions, RuleSet};
10
11pub const COPPERLACE_OK: c_int = 0;
13pub const COPPERLACE_INVALID_ARGUMENT: c_int = 1;
15pub const COPPERLACE_PARSE_ERROR: c_int = 2;
17pub const COPPERLACE_RENDER_ERROR: c_int = 3;
19
20pub type CopperlaceProcessorCallback =
26 unsafe extern "C" fn(*const c_char, *mut CopperlaceProcessorResult, *mut c_void) -> c_int;
27
28pub struct CopperlaceRuleSet {
34 ruleset: RuleSet,
35}
36
37pub struct CopperlaceProcessorResult {
39 output: Option<String>,
40 error: Option<String>,
41}
42
43struct CallbackProcessor {
44 callback: CopperlaceProcessorCallback,
45 user_data: *mut c_void,
46}
47
48unsafe impl Send for CallbackProcessor {}
49unsafe impl Sync for CallbackProcessor {}
50
51impl Processor for CallbackProcessor {
52 fn process(&self, value: &str) -> Result<String, String> {
53 let input =
54 CString::new(value).map_err(|_| "processor input contains an interior NUL byte")?;
55 let mut result = CopperlaceProcessorResult {
56 output: None,
57 error: None,
58 };
59 let status = unsafe { (self.callback)(input.as_ptr(), &mut result, self.user_data) };
60
61 if let Some(error) = result.error {
62 return Err(error);
63 }
64 if status != COPPERLACE_OK {
65 return Err(format!("processor callback failed with status {status}"));
66 }
67 result
68 .output
69 .ok_or_else(|| "processor callback did not set output".to_string())
70 }
71}
72
73#[unsafe(no_mangle)]
87pub unsafe extern "C" fn copperlace_ruleset_from_file(
88 path: *const c_char,
89 out_handle: *mut *mut CopperlaceRuleSet,
90 out_error: *mut *mut c_char,
91) -> c_int {
92 clear_out_error(out_error);
93
94 let Some(path) = read_c_string(path, out_error) else {
95 write_null_handle(out_handle);
96 return COPPERLACE_INVALID_ARGUMENT;
97 };
98
99 match ruleset_from_file(path) {
100 Ok(ruleset) => write_handle(ruleset, out_handle, out_error),
101 Err(error) => {
102 write_null_handle(out_handle);
103 write_out_string(out_error, &error.to_string());
104 COPPERLACE_PARSE_ERROR
105 }
106 }
107}
108
109#[unsafe(no_mangle)]
122pub unsafe extern "C" fn copperlace_ruleset_from_file_with_processors(
123 path: *const c_char,
124 processor_names: *const *const c_char,
125 processor_callbacks: *const Option<CopperlaceProcessorCallback>,
126 processor_user_data: *const *mut c_void,
127 processor_len: usize,
128 out_handle: *mut *mut CopperlaceRuleSet,
129 out_error: *mut *mut c_char,
130) -> c_int {
131 clear_out_error(out_error);
132
133 let Some(path) = read_c_string(path, out_error) else {
134 write_null_handle(out_handle);
135 return COPPERLACE_INVALID_ARGUMENT;
136 };
137 let Some(processors) = read_processors(
138 processor_names,
139 processor_callbacks,
140 processor_user_data,
141 processor_len,
142 out_error,
143 ) else {
144 write_null_handle(out_handle);
145 return COPPERLACE_INVALID_ARGUMENT;
146 };
147
148 match load_file_with_processors(path, processors) {
149 Ok(ruleset) => write_handle(ruleset, out_handle, out_error),
150 Err(error) => {
151 write_null_handle(out_handle);
152 write_out_string(out_error, &error.to_string());
153 COPPERLACE_PARSE_ERROR
154 }
155 }
156}
157
158#[unsafe(no_mangle)]
172pub unsafe extern "C" fn copperlace_ruleset_from_string(
173 config: *const c_char,
174 out_handle: *mut *mut CopperlaceRuleSet,
175 out_error: *mut *mut c_char,
176) -> c_int {
177 clear_out_error(out_error);
178
179 let Some(config) = read_c_string(config, out_error) else {
180 write_null_handle(out_handle);
181 return COPPERLACE_INVALID_ARGUMENT;
182 };
183
184 match ruleset_from_str(&config) {
185 Ok(ruleset) => write_handle(ruleset, out_handle, out_error),
186 Err(error) => {
187 write_null_handle(out_handle);
188 write_out_string(out_error, &error.to_string());
189 COPPERLACE_PARSE_ERROR
190 }
191 }
192}
193
194#[unsafe(no_mangle)]
207pub unsafe extern "C" fn copperlace_ruleset_from_string_with_processors(
208 config: *const c_char,
209 processor_names: *const *const c_char,
210 processor_callbacks: *const Option<CopperlaceProcessorCallback>,
211 processor_user_data: *const *mut c_void,
212 processor_len: usize,
213 out_handle: *mut *mut CopperlaceRuleSet,
214 out_error: *mut *mut c_char,
215) -> c_int {
216 clear_out_error(out_error);
217
218 let Some(config) = read_c_string(config, out_error) else {
219 write_null_handle(out_handle);
220 return COPPERLACE_INVALID_ARGUMENT;
221 };
222 let Some(processors) = read_processors(
223 processor_names,
224 processor_callbacks,
225 processor_user_data,
226 processor_len,
227 out_error,
228 ) else {
229 write_null_handle(out_handle);
230 return COPPERLACE_INVALID_ARGUMENT;
231 };
232
233 match load_str_with_processors(&config, processors) {
234 Ok(ruleset) => write_handle(ruleset, out_handle, out_error),
235 Err(error) => {
236 write_null_handle(out_handle);
237 write_out_string(out_error, &error.to_string());
238 COPPERLACE_PARSE_ERROR
239 }
240 }
241}
242
243#[unsafe(no_mangle)]
250pub unsafe extern "C" fn copperlace_processor_result_set_output(
251 result: *mut CopperlaceProcessorResult,
252 value: *const c_char,
253) -> c_int {
254 if result.is_null() {
255 return COPPERLACE_INVALID_ARGUMENT;
256 }
257 let Some(value) = read_c_string(value, ptr::null_mut()) else {
258 return COPPERLACE_INVALID_ARGUMENT;
259 };
260 unsafe {
261 (*result).output = Some(value);
262 }
263 COPPERLACE_OK
264}
265
266#[unsafe(no_mangle)]
273pub unsafe extern "C" fn copperlace_processor_result_set_error(
274 result: *mut CopperlaceProcessorResult,
275 message: *const c_char,
276) -> c_int {
277 if result.is_null() {
278 return COPPERLACE_INVALID_ARGUMENT;
279 }
280 let Some(message) = read_c_string(message, ptr::null_mut()) else {
281 return COPPERLACE_INVALID_ARGUMENT;
282 };
283 unsafe {
284 (*result).error = Some(message);
285 }
286 COPPERLACE_OK
287}
288
289#[unsafe(no_mangle)]
304pub unsafe extern "C" fn copperlace_ruleset_render(
305 handle: *const CopperlaceRuleSet,
306 rule: *const c_char,
307 out_string: *mut *mut c_char,
308 out_error: *mut *mut c_char,
309) -> c_int {
310 unsafe {
311 copperlace_ruleset_render_with_context(
312 handle,
313 rule,
314 ptr::null(),
315 ptr::null(),
316 0,
317 out_string,
318 out_error,
319 )
320 }
321}
322
323#[unsafe(no_mangle)]
345pub unsafe extern "C" fn copperlace_ruleset_render_with_context(
346 handle: *const CopperlaceRuleSet,
347 rule: *const c_char,
348 context_keys: *const *const c_char,
349 context_values: *const *const c_char,
350 context_len: usize,
351 out_string: *mut *mut c_char,
352 out_error: *mut *mut c_char,
353) -> c_int {
354 unsafe {
355 copperlace_ruleset_render_with_context_and_options(
356 handle,
357 rule,
358 context_keys,
359 context_values,
360 context_len,
361 0,
362 out_string,
363 out_error,
364 )
365 }
366}
367
368#[unsafe(no_mangle)]
384pub unsafe extern "C" fn copperlace_ruleset_render_with_context_and_options(
385 handle: *const CopperlaceRuleSet,
386 rule: *const c_char,
387 context_keys: *const *const c_char,
388 context_values: *const *const c_char,
389 context_len: usize,
390 max_recursion_depth: usize,
391 out_string: *mut *mut c_char,
392 out_error: *mut *mut c_char,
393) -> c_int {
394 clear_out_error(out_error);
395 write_null_string(out_string);
396
397 if handle.is_null() {
398 write_out_string(out_error, "ruleset handle is null");
399 return COPPERLACE_INVALID_ARGUMENT;
400 }
401
402 let Some(rule) = read_c_string(rule, out_error) else {
403 return COPPERLACE_INVALID_ARGUMENT;
404 };
405 let Some(context) = read_context(context_keys, context_values, context_len, out_error) else {
406 return COPPERLACE_INVALID_ARGUMENT;
407 };
408
409 let ruleset = unsafe { &(*handle).ruleset };
410 let options = RenderOptions {
411 max_recursion_depth,
412 };
413 match ruleset.render_rule_with_context_and_options(&rule, context, options) {
414 Ok(output) => {
415 if write_out_string(out_string, &output) {
416 COPPERLACE_OK
417 } else {
418 write_out_string(out_error, "output string contains an interior NUL byte");
419 COPPERLACE_RENDER_ERROR
420 }
421 }
422 Err(error) => {
423 write_out_string(out_error, &error.to_string());
424 COPPERLACE_RENDER_ERROR
425 }
426 }
427}
428
429#[unsafe(no_mangle)]
441pub unsafe extern "C" fn copperlace_ruleset_render_inferred(
442 handle: *const CopperlaceRuleSet,
443 rule: *const c_char,
444 out_string: *mut *mut c_char,
445 out_error: *mut *mut c_char,
446) -> c_int {
447 unsafe {
448 copperlace_ruleset_render_inferred_with_context(
449 handle,
450 rule,
451 ptr::null(),
452 ptr::null(),
453 0,
454 out_string,
455 out_error,
456 )
457 }
458}
459
460#[unsafe(no_mangle)]
472pub unsafe extern "C" fn copperlace_ruleset_render_inferred_with_context(
473 handle: *const CopperlaceRuleSet,
474 rule: *const c_char,
475 context_keys: *const *const c_char,
476 context_values: *const *const c_char,
477 context_len: usize,
478 out_string: *mut *mut c_char,
479 out_error: *mut *mut c_char,
480) -> c_int {
481 unsafe {
482 copperlace_ruleset_render_inferred_with_context_and_options(
483 handle,
484 rule,
485 context_keys,
486 context_values,
487 context_len,
488 0,
489 out_string,
490 out_error,
491 )
492 }
493}
494
495#[unsafe(no_mangle)]
507pub unsafe extern "C" fn copperlace_ruleset_render_inferred_with_context_and_options(
508 handle: *const CopperlaceRuleSet,
509 rule: *const c_char,
510 context_keys: *const *const c_char,
511 context_values: *const *const c_char,
512 context_len: usize,
513 max_recursion_depth: usize,
514 out_string: *mut *mut c_char,
515 out_error: *mut *mut c_char,
516) -> c_int {
517 clear_out_error(out_error);
518 write_null_string(out_string);
519
520 if handle.is_null() {
521 write_out_string(out_error, "ruleset handle is null");
522 return COPPERLACE_INVALID_ARGUMENT;
523 }
524
525 let Some(rule) = read_c_string(rule, out_error) else {
526 return COPPERLACE_INVALID_ARGUMENT;
527 };
528 let Some(context) = read_context(context_keys, context_values, context_len, out_error) else {
529 return COPPERLACE_INVALID_ARGUMENT;
530 };
531
532 let ruleset = unsafe { &(*handle).ruleset };
533 let options = RenderOptions {
534 max_recursion_depth,
535 };
536 match ruleset.render_rule_inferred_with_context_and_options(&rule, context, options) {
537 Ok(output) => {
538 if write_out_string(out_string, &output) {
539 COPPERLACE_OK
540 } else {
541 write_out_string(out_error, "output string contains an interior NUL byte");
542 COPPERLACE_RENDER_ERROR
543 }
544 }
545 Err(error) => {
546 write_out_string(out_error, &error.to_string());
547 COPPERLACE_RENDER_ERROR
548 }
549 }
550}
551
552#[unsafe(no_mangle)]
569pub unsafe extern "C" fn copperlace_ruleset_render_structured_json(
570 handle: *const CopperlaceRuleSet,
571 rule: *const c_char,
572 format_json: bool,
573 out_json: *mut *mut c_char,
574 out_error: *mut *mut c_char,
575) -> c_int {
576 unsafe {
577 copperlace_ruleset_render_structured_json_with_context(
578 handle,
579 rule,
580 ptr::null(),
581 ptr::null(),
582 0,
583 format_json,
584 out_json,
585 out_error,
586 )
587 }
588}
589
590#[unsafe(no_mangle)]
613pub unsafe extern "C" fn copperlace_ruleset_render_structured_json_with_context(
614 handle: *const CopperlaceRuleSet,
615 rule: *const c_char,
616 context_keys: *const *const c_char,
617 context_values: *const *const c_char,
618 context_len: usize,
619 format_json: bool,
620 out_json: *mut *mut c_char,
621 out_error: *mut *mut c_char,
622) -> c_int {
623 unsafe {
624 copperlace_ruleset_render_structured_json_with_context_and_options(
625 handle,
626 rule,
627 context_keys,
628 context_values,
629 context_len,
630 format_json,
631 0,
632 out_json,
633 out_error,
634 )
635 }
636}
637
638#[unsafe(no_mangle)]
650pub unsafe extern "C" fn copperlace_ruleset_render_structured_json_with_context_and_options(
651 handle: *const CopperlaceRuleSet,
652 rule: *const c_char,
653 context_keys: *const *const c_char,
654 context_values: *const *const c_char,
655 context_len: usize,
656 format_json: bool,
657 max_recursion_depth: usize,
658 out_json: *mut *mut c_char,
659 out_error: *mut *mut c_char,
660) -> c_int {
661 clear_out_error(out_error);
662
663 if out_json.is_null() {
664 write_out_string(out_error, "out_json is null");
665 return COPPERLACE_INVALID_ARGUMENT;
666 }
667 write_null_string(out_json);
668
669 if handle.is_null() {
670 write_out_string(out_error, "ruleset handle is null");
671 return COPPERLACE_INVALID_ARGUMENT;
672 }
673
674 let Some(rule) = read_c_string(rule, out_error) else {
675 return COPPERLACE_INVALID_ARGUMENT;
676 };
677 let Some(context) = read_context(context_keys, context_values, context_len, out_error) else {
678 return COPPERLACE_INVALID_ARGUMENT;
679 };
680
681 let ruleset = unsafe { &(*handle).ruleset };
682 let options = RenderOptions {
683 max_recursion_depth,
684 };
685 let json = ruleset
686 .render_rule_structured_with_context_and_options(&rule, context, options)
687 .and_then(|value| {
688 if format_json {
689 value.to_formatted_json()
690 } else {
691 value.to_compact_json()
692 }
693 });
694
695 match json {
696 Ok(output) => {
697 if write_out_string(out_json, &output) {
698 COPPERLACE_OK
699 } else {
700 write_out_string(out_error, "structured JSON contains an interior NUL byte");
701 COPPERLACE_RENDER_ERROR
702 }
703 }
704 Err(error) => {
705 write_out_string(out_error, &error.to_string());
706 COPPERLACE_RENDER_ERROR
707 }
708 }
709}
710
711fn read_context(
712 keys: *const *const c_char,
713 values: *const *const c_char,
714 len: usize,
715 out_error: *mut *mut c_char,
716) -> Option<RenderContext> {
717 let mut context = RenderContext::new();
718 if len == 0 {
719 return Some(context);
720 }
721 if keys.is_null() {
722 write_out_string(out_error, "context keys array is null");
723 return None;
724 }
725 if values.is_null() {
726 write_out_string(out_error, "context values array is null");
727 return None;
728 }
729
730 for index in 0..len {
731 let key_ptr = unsafe { *keys.add(index) };
732 let value_ptr = unsafe { *values.add(index) };
733 let key = read_c_string(key_ptr, out_error)?;
734 let value = read_c_string(value_ptr, out_error)?;
735 context.insert(key, value);
736 }
737
738 Some(context)
739}
740
741fn read_processors(
742 names: *const *const c_char,
743 callbacks: *const Option<CopperlaceProcessorCallback>,
744 user_data: *const *mut c_void,
745 len: usize,
746 out_error: *mut *mut c_char,
747) -> Option<ProcessorRegistry> {
748 let mut processors = ProcessorRegistry::new();
749 if len == 0 {
750 return Some(processors);
751 }
752 if names.is_null() {
753 write_out_string(out_error, "processor names array is null");
754 return None;
755 }
756 if callbacks.is_null() {
757 write_out_string(out_error, "processor callbacks array is null");
758 return None;
759 }
760 if user_data.is_null() {
761 write_out_string(out_error, "processor user data array is null");
762 return None;
763 }
764
765 for index in 0..len {
766 let name_ptr = unsafe { *names.add(index) };
767 let callback = unsafe { *callbacks.add(index) };
768 let Some(callback) = callback else {
769 write_out_string(out_error, "processor callback is null");
770 return None;
771 };
772 let name = read_c_string(name_ptr, out_error)?;
773 let user_data = unsafe { *user_data.add(index) };
774 processors.insert(
775 name,
776 std::sync::Arc::new(CallbackProcessor {
777 callback,
778 user_data,
779 }),
780 );
781 }
782
783 Some(processors)
784}
785
786#[unsafe(no_mangle)]
795pub unsafe extern "C" fn copperlace_ruleset_free(handle: *mut CopperlaceRuleSet) {
796 if !handle.is_null() {
797 unsafe {
798 drop(Box::from_raw(handle));
799 }
800 }
801}
802
803#[unsafe(no_mangle)]
813pub unsafe extern "C" fn copperlace_string_free(value: *mut c_char) {
814 if !value.is_null() {
815 unsafe {
816 drop(CString::from_raw(value));
817 }
818 }
819}
820
821fn read_c_string(value: *const c_char, out_error: *mut *mut c_char) -> Option<String> {
822 if value.is_null() {
823 write_out_string(out_error, "input string is null");
824 return None;
825 }
826
827 match unsafe { CStr::from_ptr(value) }.to_str() {
828 Ok(value) => Some(value.to_string()),
829 Err(error) => {
830 write_out_string(
831 out_error,
832 &format!("input string is not valid UTF-8: {error}"),
833 );
834 None
835 }
836 }
837}
838
839fn write_handle(
840 ruleset: RuleSet,
841 out_handle: *mut *mut CopperlaceRuleSet,
842 out_error: *mut *mut c_char,
843) -> c_int {
844 if out_handle.is_null() {
845 write_out_string(out_error, "out_handle is null");
846 return COPPERLACE_INVALID_ARGUMENT;
847 }
848
849 let handle = Box::into_raw(Box::new(CopperlaceRuleSet { ruleset }));
850 unsafe {
851 *out_handle = handle;
852 }
853 COPPERLACE_OK
854}
855
856fn write_null_handle(out_handle: *mut *mut CopperlaceRuleSet) {
857 if !out_handle.is_null() {
858 unsafe {
859 *out_handle = ptr::null_mut();
860 }
861 }
862}
863
864fn write_null_string(out_string: *mut *mut c_char) {
865 if !out_string.is_null() {
866 unsafe {
867 *out_string = ptr::null_mut();
868 }
869 }
870}
871
872fn clear_out_error(out_error: *mut *mut c_char) {
873 write_null_string(out_error);
874}
875
876fn write_out_string(out_string: *mut *mut c_char, value: &str) -> bool {
877 if out_string.is_null() {
878 return true;
879 }
880
881 let Ok(value) = CString::new(value) else {
882 return false;
883 };
884
885 unsafe {
886 *out_string = value.into_raw();
887 }
888 true
889}