1use std::ffi::{CStr, CString};
2use std::os::raw::{c_char, c_int, c_void};
3use std::ptr;
4
5use crate::config::{ConfigError, ruleset_from_file, ruleset_from_str};
6use crate::render::{Processor, ProcessorRegistry, RenderContext, RuleSet};
7
8pub const COPPERLACE_OK: c_int = 0;
10pub const COPPERLACE_INVALID_ARGUMENT: c_int = 1;
12pub const COPPERLACE_PARSE_ERROR: c_int = 2;
14pub const COPPERLACE_RENDER_ERROR: c_int = 3;
16
17pub type CopperlaceProcessorCallback =
23 unsafe extern "C" fn(*const c_char, *mut CopperlaceProcessorResult, *mut c_void) -> c_int;
24
25pub struct CopperlaceRuleSet {
31 ruleset: RuleSet,
32}
33
34pub struct CopperlaceProcessorResult {
36 output: Option<String>,
37 error: Option<String>,
38}
39
40struct CallbackProcessor {
41 callback: CopperlaceProcessorCallback,
42 user_data: *mut c_void,
43}
44
45unsafe impl Send for CallbackProcessor {}
46unsafe impl Sync for CallbackProcessor {}
47
48impl Processor for CallbackProcessor {
49 fn process(&self, value: &str) -> Result<String, String> {
50 let input =
51 CString::new(value).map_err(|_| "processor input contains an interior NUL byte")?;
52 let mut result = CopperlaceProcessorResult {
53 output: None,
54 error: None,
55 };
56 let status = unsafe { (self.callback)(input.as_ptr(), &mut result, self.user_data) };
57
58 if let Some(error) = result.error {
59 return Err(error);
60 }
61 if status != COPPERLACE_OK {
62 return Err(format!("processor callback failed with status {status}"));
63 }
64 result
65 .output
66 .ok_or_else(|| "processor callback did not set output".to_string())
67 }
68}
69
70#[unsafe(no_mangle)]
84pub unsafe extern "C" fn copperlace_ruleset_from_file(
85 path: *const c_char,
86 out_handle: *mut *mut CopperlaceRuleSet,
87 out_error: *mut *mut c_char,
88) -> c_int {
89 clear_out_error(out_error);
90
91 let Some(path) = read_c_string(path, out_error) else {
92 write_null_handle(out_handle);
93 return COPPERLACE_INVALID_ARGUMENT;
94 };
95
96 match ruleset_from_file(path) {
97 Ok(ruleset) => write_handle(ruleset, out_handle, out_error),
98 Err(error) => {
99 write_null_handle(out_handle);
100 write_out_string(out_error, &error.to_string());
101 COPPERLACE_PARSE_ERROR
102 }
103 }
104}
105
106#[unsafe(no_mangle)]
119pub unsafe extern "C" fn copperlace_ruleset_from_file_with_processors(
120 path: *const c_char,
121 processor_names: *const *const c_char,
122 processor_callbacks: *const Option<CopperlaceProcessorCallback>,
123 processor_user_data: *const *mut c_void,
124 processor_len: usize,
125 out_handle: *mut *mut CopperlaceRuleSet,
126 out_error: *mut *mut c_char,
127) -> c_int {
128 clear_out_error(out_error);
129
130 let Some(path) = read_c_string(path, out_error) else {
131 write_null_handle(out_handle);
132 return COPPERLACE_INVALID_ARGUMENT;
133 };
134 let Some(processors) = read_processors(
135 processor_names,
136 processor_callbacks,
137 processor_user_data,
138 processor_len,
139 out_error,
140 ) else {
141 write_null_handle(out_handle);
142 return COPPERLACE_INVALID_ARGUMENT;
143 };
144
145 match ruleset_from_file_with_processors(path, processors) {
146 Ok(ruleset) => write_handle(ruleset, out_handle, out_error),
147 Err(error) => {
148 write_null_handle(out_handle);
149 write_out_string(out_error, &error.to_string());
150 COPPERLACE_PARSE_ERROR
151 }
152 }
153}
154
155#[unsafe(no_mangle)]
169pub unsafe extern "C" fn copperlace_ruleset_from_string(
170 config: *const c_char,
171 out_handle: *mut *mut CopperlaceRuleSet,
172 out_error: *mut *mut c_char,
173) -> c_int {
174 clear_out_error(out_error);
175
176 let Some(config) = read_c_string(config, out_error) else {
177 write_null_handle(out_handle);
178 return COPPERLACE_INVALID_ARGUMENT;
179 };
180
181 match ruleset_from_str(&config) {
182 Ok(ruleset) => write_handle(ruleset, out_handle, out_error),
183 Err(error) => {
184 write_null_handle(out_handle);
185 write_out_string(out_error, &error.to_string());
186 COPPERLACE_PARSE_ERROR
187 }
188 }
189}
190
191#[unsafe(no_mangle)]
204pub unsafe extern "C" fn copperlace_ruleset_from_string_with_processors(
205 config: *const c_char,
206 processor_names: *const *const c_char,
207 processor_callbacks: *const Option<CopperlaceProcessorCallback>,
208 processor_user_data: *const *mut c_void,
209 processor_len: usize,
210 out_handle: *mut *mut CopperlaceRuleSet,
211 out_error: *mut *mut c_char,
212) -> c_int {
213 clear_out_error(out_error);
214
215 let Some(config) = read_c_string(config, out_error) else {
216 write_null_handle(out_handle);
217 return COPPERLACE_INVALID_ARGUMENT;
218 };
219 let Some(processors) = read_processors(
220 processor_names,
221 processor_callbacks,
222 processor_user_data,
223 processor_len,
224 out_error,
225 ) else {
226 write_null_handle(out_handle);
227 return COPPERLACE_INVALID_ARGUMENT;
228 };
229
230 match ruleset_from_str_with_processors(&config, processors) {
231 Ok(ruleset) => write_handle(ruleset, out_handle, out_error),
232 Err(error) => {
233 write_null_handle(out_handle);
234 write_out_string(out_error, &error.to_string());
235 COPPERLACE_PARSE_ERROR
236 }
237 }
238}
239
240#[unsafe(no_mangle)]
247pub unsafe extern "C" fn copperlace_processor_result_set_output(
248 result: *mut CopperlaceProcessorResult,
249 value: *const c_char,
250) -> c_int {
251 if result.is_null() {
252 return COPPERLACE_INVALID_ARGUMENT;
253 }
254 let Some(value) = read_c_string(value, ptr::null_mut()) else {
255 return COPPERLACE_INVALID_ARGUMENT;
256 };
257 unsafe {
258 (*result).output = Some(value);
259 }
260 COPPERLACE_OK
261}
262
263#[unsafe(no_mangle)]
270pub unsafe extern "C" fn copperlace_processor_result_set_error(
271 result: *mut CopperlaceProcessorResult,
272 message: *const c_char,
273) -> c_int {
274 if result.is_null() {
275 return COPPERLACE_INVALID_ARGUMENT;
276 }
277 let Some(message) = read_c_string(message, ptr::null_mut()) else {
278 return COPPERLACE_INVALID_ARGUMENT;
279 };
280 unsafe {
281 (*result).error = Some(message);
282 }
283 COPPERLACE_OK
284}
285
286#[unsafe(no_mangle)]
301pub unsafe extern "C" fn copperlace_ruleset_render(
302 handle: *const CopperlaceRuleSet,
303 rule: *const c_char,
304 out_string: *mut *mut c_char,
305 out_error: *mut *mut c_char,
306) -> c_int {
307 unsafe {
308 copperlace_ruleset_render_with_context(
309 handle,
310 rule,
311 ptr::null(),
312 ptr::null(),
313 0,
314 out_string,
315 out_error,
316 )
317 }
318}
319
320#[unsafe(no_mangle)]
342pub unsafe extern "C" fn copperlace_ruleset_render_with_context(
343 handle: *const CopperlaceRuleSet,
344 rule: *const c_char,
345 context_keys: *const *const c_char,
346 context_values: *const *const c_char,
347 context_len: usize,
348 out_string: *mut *mut c_char,
349 out_error: *mut *mut c_char,
350) -> c_int {
351 clear_out_error(out_error);
352 write_null_string(out_string);
353
354 if handle.is_null() {
355 write_out_string(out_error, "ruleset handle is null");
356 return COPPERLACE_INVALID_ARGUMENT;
357 }
358
359 let Some(rule) = read_c_string(rule, out_error) else {
360 return COPPERLACE_INVALID_ARGUMENT;
361 };
362 let Some(context) = read_context(context_keys, context_values, context_len, out_error) else {
363 return COPPERLACE_INVALID_ARGUMENT;
364 };
365
366 let ruleset = unsafe { &(*handle).ruleset };
367 match ruleset.render_rule_with_context(&rule, context) {
368 Ok(output) => {
369 if write_out_string(out_string, &output) {
370 COPPERLACE_OK
371 } else {
372 write_out_string(out_error, "output string contains an interior NUL byte");
373 COPPERLACE_RENDER_ERROR
374 }
375 }
376 Err(error) => {
377 write_out_string(out_error, &error.to_string());
378 COPPERLACE_RENDER_ERROR
379 }
380 }
381}
382
383#[unsafe(no_mangle)]
395pub unsafe extern "C" fn copperlace_ruleset_render_inferred(
396 handle: *const CopperlaceRuleSet,
397 rule: *const c_char,
398 out_string: *mut *mut c_char,
399 out_error: *mut *mut c_char,
400) -> c_int {
401 unsafe {
402 copperlace_ruleset_render_inferred_with_context(
403 handle,
404 rule,
405 ptr::null(),
406 ptr::null(),
407 0,
408 out_string,
409 out_error,
410 )
411 }
412}
413
414#[unsafe(no_mangle)]
426pub unsafe extern "C" fn copperlace_ruleset_render_inferred_with_context(
427 handle: *const CopperlaceRuleSet,
428 rule: *const c_char,
429 context_keys: *const *const c_char,
430 context_values: *const *const c_char,
431 context_len: usize,
432 out_string: *mut *mut c_char,
433 out_error: *mut *mut c_char,
434) -> c_int {
435 clear_out_error(out_error);
436 write_null_string(out_string);
437
438 if handle.is_null() {
439 write_out_string(out_error, "ruleset handle is null");
440 return COPPERLACE_INVALID_ARGUMENT;
441 }
442
443 let Some(rule) = read_c_string(rule, out_error) else {
444 return COPPERLACE_INVALID_ARGUMENT;
445 };
446 let Some(context) = read_context(context_keys, context_values, context_len, out_error) else {
447 return COPPERLACE_INVALID_ARGUMENT;
448 };
449
450 let ruleset = unsafe { &(*handle).ruleset };
451 match ruleset.render_rule_inferred_with_context(&rule, context) {
452 Ok(output) => {
453 if write_out_string(out_string, &output) {
454 COPPERLACE_OK
455 } else {
456 write_out_string(out_error, "output string contains an interior NUL byte");
457 COPPERLACE_RENDER_ERROR
458 }
459 }
460 Err(error) => {
461 write_out_string(out_error, &error.to_string());
462 COPPERLACE_RENDER_ERROR
463 }
464 }
465}
466
467#[unsafe(no_mangle)]
484pub unsafe extern "C" fn copperlace_ruleset_render_structured_json(
485 handle: *const CopperlaceRuleSet,
486 rule: *const c_char,
487 format_json: bool,
488 out_json: *mut *mut c_char,
489 out_error: *mut *mut c_char,
490) -> c_int {
491 unsafe {
492 copperlace_ruleset_render_structured_json_with_context(
493 handle,
494 rule,
495 ptr::null(),
496 ptr::null(),
497 0,
498 format_json,
499 out_json,
500 out_error,
501 )
502 }
503}
504
505#[unsafe(no_mangle)]
528pub unsafe extern "C" fn copperlace_ruleset_render_structured_json_with_context(
529 handle: *const CopperlaceRuleSet,
530 rule: *const c_char,
531 context_keys: *const *const c_char,
532 context_values: *const *const c_char,
533 context_len: usize,
534 format_json: bool,
535 out_json: *mut *mut c_char,
536 out_error: *mut *mut c_char,
537) -> c_int {
538 clear_out_error(out_error);
539
540 if out_json.is_null() {
541 write_out_string(out_error, "out_json is null");
542 return COPPERLACE_INVALID_ARGUMENT;
543 }
544 write_null_string(out_json);
545
546 if handle.is_null() {
547 write_out_string(out_error, "ruleset handle is null");
548 return COPPERLACE_INVALID_ARGUMENT;
549 }
550
551 let Some(rule) = read_c_string(rule, out_error) else {
552 return COPPERLACE_INVALID_ARGUMENT;
553 };
554 let Some(context) = read_context(context_keys, context_values, context_len, out_error) else {
555 return COPPERLACE_INVALID_ARGUMENT;
556 };
557
558 let ruleset = unsafe { &(*handle).ruleset };
559 let json = ruleset
560 .render_rule_structured_with_context(&rule, context)
561 .and_then(|value| {
562 if format_json {
563 value.to_formatted_json()
564 } else {
565 value.to_compact_json()
566 }
567 });
568
569 match json {
570 Ok(output) => {
571 if write_out_string(out_json, &output) {
572 COPPERLACE_OK
573 } else {
574 write_out_string(out_error, "structured JSON contains an interior NUL byte");
575 COPPERLACE_RENDER_ERROR
576 }
577 }
578 Err(error) => {
579 write_out_string(out_error, &error.to_string());
580 COPPERLACE_RENDER_ERROR
581 }
582 }
583}
584
585fn read_context(
586 keys: *const *const c_char,
587 values: *const *const c_char,
588 len: usize,
589 out_error: *mut *mut c_char,
590) -> Option<RenderContext> {
591 let mut context = RenderContext::new();
592 if len == 0 {
593 return Some(context);
594 }
595 if keys.is_null() {
596 write_out_string(out_error, "context keys array is null");
597 return None;
598 }
599 if values.is_null() {
600 write_out_string(out_error, "context values array is null");
601 return None;
602 }
603
604 for index in 0..len {
605 let key_ptr = unsafe { *keys.add(index) };
606 let value_ptr = unsafe { *values.add(index) };
607 let key = read_c_string(key_ptr, out_error)?;
608 let value = read_c_string(value_ptr, out_error)?;
609 context.insert(key, value);
610 }
611
612 Some(context)
613}
614
615fn read_processors(
616 names: *const *const c_char,
617 callbacks: *const Option<CopperlaceProcessorCallback>,
618 user_data: *const *mut c_void,
619 len: usize,
620 out_error: *mut *mut c_char,
621) -> Option<ProcessorRegistry> {
622 let mut processors = ProcessorRegistry::new();
623 if len == 0 {
624 return Some(processors);
625 }
626 if names.is_null() {
627 write_out_string(out_error, "processor names array is null");
628 return None;
629 }
630 if callbacks.is_null() {
631 write_out_string(out_error, "processor callbacks array is null");
632 return None;
633 }
634 if user_data.is_null() {
635 write_out_string(out_error, "processor user data array is null");
636 return None;
637 }
638
639 for index in 0..len {
640 let name_ptr = unsafe { *names.add(index) };
641 let callback = unsafe { *callbacks.add(index) };
642 let Some(callback) = callback else {
643 write_out_string(out_error, "processor callback is null");
644 return None;
645 };
646 let name = read_c_string(name_ptr, out_error)?;
647 let user_data = unsafe { *user_data.add(index) };
648 processors.insert(
649 name,
650 std::sync::Arc::new(CallbackProcessor {
651 callback,
652 user_data,
653 }),
654 );
655 }
656
657 Some(processors)
658}
659
660fn ruleset_from_str_with_processors(
661 config: &str,
662 processors: ProcessorRegistry,
663) -> Result<RuleSet, ConfigError> {
664 let value = hocon_rs::Config::parse_str::<hocon_rs::Value>(config, None)
665 .map_err(|error| ConfigError::Parse(format!("{error:?}")))?;
666 RuleSet::from_config_with_processors(value, processors).map_err(ConfigError::Render)
667}
668
669fn ruleset_from_file_with_processors(
670 path: String,
671 processors: ProcessorRegistry,
672) -> Result<RuleSet, ConfigError> {
673 let value = hocon_rs::Config::load(&path, None)
674 .map_err(|error| ConfigError::Parse(format!("{error:?}")))?;
675 RuleSet::from_config_with_processors(value, processors).map_err(ConfigError::Render)
676}
677
678#[unsafe(no_mangle)]
687pub unsafe extern "C" fn copperlace_ruleset_free(handle: *mut CopperlaceRuleSet) {
688 if !handle.is_null() {
689 unsafe {
690 drop(Box::from_raw(handle));
691 }
692 }
693}
694
695#[unsafe(no_mangle)]
705pub unsafe extern "C" fn copperlace_string_free(value: *mut c_char) {
706 if !value.is_null() {
707 unsafe {
708 drop(CString::from_raw(value));
709 }
710 }
711}
712
713fn read_c_string(value: *const c_char, out_error: *mut *mut c_char) -> Option<String> {
714 if value.is_null() {
715 write_out_string(out_error, "input string is null");
716 return None;
717 }
718
719 match unsafe { CStr::from_ptr(value) }.to_str() {
720 Ok(value) => Some(value.to_string()),
721 Err(error) => {
722 write_out_string(
723 out_error,
724 &format!("input string is not valid UTF-8: {error}"),
725 );
726 None
727 }
728 }
729}
730
731fn write_handle(
732 ruleset: RuleSet,
733 out_handle: *mut *mut CopperlaceRuleSet,
734 out_error: *mut *mut c_char,
735) -> c_int {
736 if out_handle.is_null() {
737 write_out_string(out_error, "out_handle is null");
738 return COPPERLACE_INVALID_ARGUMENT;
739 }
740
741 let handle = Box::into_raw(Box::new(CopperlaceRuleSet { ruleset }));
742 unsafe {
743 *out_handle = handle;
744 }
745 COPPERLACE_OK
746}
747
748fn write_null_handle(out_handle: *mut *mut CopperlaceRuleSet) {
749 if !out_handle.is_null() {
750 unsafe {
751 *out_handle = ptr::null_mut();
752 }
753 }
754}
755
756fn write_null_string(out_string: *mut *mut c_char) {
757 if !out_string.is_null() {
758 unsafe {
759 *out_string = ptr::null_mut();
760 }
761 }
762}
763
764fn clear_out_error(out_error: *mut *mut c_char) {
765 write_null_string(out_error);
766}
767
768fn write_out_string(out_string: *mut *mut c_char, value: &str) -> bool {
769 if out_string.is_null() {
770 return true;
771 }
772
773 let Ok(value) = CString::new(value) else {
774 return false;
775 };
776
777 unsafe {
778 *out_string = value.into_raw();
779 }
780 true
781}