Skip to main content

copperlace/
ffi.rs

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
11/// Status code for a successful C ABI call.
12pub const COPPERLACE_OK: c_int = 0;
13/// Status code for invalid C ABI arguments such as null required pointers.
14pub const COPPERLACE_INVALID_ARGUMENT: c_int = 1;
15/// Status code for config loading, parsing, or compilation failures.
16pub const COPPERLACE_PARSE_ERROR: c_int = 2;
17/// Status code for rule rendering failures.
18pub const COPPERLACE_RENDER_ERROR: c_int = 3;
19
20/// Host callback used by custom C ABI processors.
21///
22/// The callback receives a UTF-8 input string, an opaque result handle, and the
23/// user data pointer provided when creating the ruleset. It should set either
24/// output or error on `result` and return [`COPPERLACE_OK`] on success.
25pub type CopperlaceProcessorCallback =
26    unsafe extern "C" fn(*const c_char, *mut CopperlaceProcessorResult, *mut c_void) -> c_int;
27
28/// Opaque C ABI handle for a compiled Copperlace ruleset.
29///
30/// Handles are allocated by `copperlace_ruleset_from_file` or
31/// `copperlace_ruleset_from_string` and must be released with
32/// `copperlace_ruleset_free`.
33pub struct CopperlaceRuleSet {
34    ruleset: RuleSet,
35}
36
37/// Opaque C ABI result handle passed to custom processor callbacks.
38pub 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/// Loads a configuration file and returns an opaque ruleset handle.
74///
75/// On success, writes a non-null handle to `out_handle` and returns
76/// [`COPPERLACE_OK`]. On failure, writes null to `out_handle`, writes an owned
77/// error string to `out_error` when provided, and returns a nonzero status code.
78/// Returned error strings must be released with `copperlace_string_free`.
79///
80/// # Safety
81///
82/// `path` must point to a valid NUL-terminated UTF-8 string. `out_handle` must
83/// be a valid writable pointer when non-null. `out_error` must be valid for
84/// writing when non-null, and any returned error string must be released with
85/// [`copperlace_string_free`].
86#[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/// Loads a configuration file and returns a ruleset handle with custom processors.
110///
111/// # Safety
112///
113/// `path` must point to a valid NUL-terminated UTF-8 string. When
114/// `processor_len` is nonzero, `processor_names`, `processor_callbacks`, and
115/// `processor_user_data` must each point to arrays with at least
116/// `processor_len` entries. Processor names must point to valid NUL-terminated
117/// UTF-8 strings. `out_handle` must be a valid writable pointer when non-null.
118/// `out_error` must be valid for writing when non-null, and any returned error
119/// string must be released with [`copperlace_string_free`]. Processor callbacks
120/// and user data must remain valid until the returned ruleset handle is freed.
121#[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/// Compiles a configuration string and returns an opaque ruleset handle.
159///
160/// On success, writes a non-null handle to `out_handle` and returns
161/// [`COPPERLACE_OK`]. On failure, writes null to `out_handle`, writes an owned
162/// error string to `out_error` when provided, and returns a nonzero status code.
163/// Returned error strings must be released with `copperlace_string_free`.
164///
165/// # Safety
166///
167/// `config` must point to a valid NUL-terminated UTF-8 string. `out_handle`
168/// must be a valid writable pointer when non-null. `out_error` must be valid
169/// for writing when non-null, and any returned error string must be released
170/// with [`copperlace_string_free`].
171#[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/// Compiles a configuration string and returns a ruleset handle with custom processors.
195///
196/// # Safety
197///
198/// `config` must point to a valid NUL-terminated UTF-8 string. When
199/// `processor_len` is nonzero, `processor_names`, `processor_callbacks`, and
200/// `processor_user_data` must each point to arrays with at least
201/// `processor_len` entries. Processor names must point to valid NUL-terminated
202/// UTF-8 strings. `out_handle` must be a valid writable pointer when non-null.
203/// `out_error` must be valid for writing when non-null, and any returned error
204/// string must be released with [`copperlace_string_free`]. Processor callbacks
205/// and user data must remain valid until the returned ruleset handle is freed.
206#[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/// Sets the output for a custom processor callback result.
244///
245/// # Safety
246///
247/// `result` must be the valid result handle passed to the active processor
248/// callback. `value` must point to a valid NUL-terminated UTF-8 string.
249#[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/// Sets the error for a custom processor callback result.
267///
268/// # Safety
269///
270/// `result` must be the valid result handle passed to the active processor
271/// callback. `message` must point to a valid NUL-terminated UTF-8 string.
272#[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/// Renders a named rule from a ruleset handle.
290///
291/// On success, writes an owned UTF-8 string to `out_string` and returns
292/// [`COPPERLACE_OK`]. On failure, writes null to `out_string`, writes an owned
293/// error string to `out_error` when provided, and returns a nonzero status code.
294/// Returned output and error strings must be released with
295/// `copperlace_string_free`.
296///
297/// # Safety
298///
299/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
300/// point to a valid NUL-terminated UTF-8 string. `out_string` and `out_error`
301/// must be valid for writing when non-null. Any returned output or error string
302/// must be released with [`copperlace_string_free`].
303#[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/// Renders a named rule from a ruleset handle with initial context values.
324///
325/// `context_keys` and `context_values` are parallel arrays of UTF-8 C strings.
326/// They may be null only when `context_len` is zero. Duplicate keys are allowed;
327/// later entries replace earlier entries.
328///
329/// On success, writes an owned UTF-8 string to `out_string` and returns
330/// [`COPPERLACE_OK`]. On failure, writes null to `out_string`, writes an owned
331/// error string to `out_error` when provided, and returns a nonzero status code.
332/// Returned output and error strings must be released with
333/// `copperlace_string_free`.
334///
335/// # Safety
336///
337/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
338/// point to a valid NUL-terminated UTF-8 string. When `context_len` is nonzero,
339/// `context_keys` and `context_values` must each point to arrays with at least
340/// `context_len` entries, and every entry must point to a valid
341/// NUL-terminated UTF-8 string. `out_string` and `out_error` must be valid for
342/// writing when non-null. Any returned output or error string must be released
343/// with [`copperlace_string_free`].
344#[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/// Renders a named rule from a ruleset handle with initial context values and render options.
369///
370/// `max_recursion_depth` controls recursive rule expansion. A value of zero
371/// preserves default circular-reference errors. Values greater than zero allow
372/// that many recursive re-entries before recursive calls return an empty string.
373///
374/// # Safety
375///
376/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
377/// point to a valid NUL-terminated UTF-8 string. When `context_len` is nonzero,
378/// `context_keys` and `context_values` must each point to arrays with at least
379/// `context_len` entries, and every entry must point to a valid
380/// NUL-terminated UTF-8 string. `out_string` and `out_error` must be valid for
381/// writing when non-null. Any returned output or error string must be released
382/// with [`copperlace_string_free`].
383#[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/// Renders a named rule, inferring formatted structured JSON for object-valued rules.
430///
431/// String-valued and list-valued rules use existing text rendering. Object-valued
432/// rules return formatted JSON using tab indentation.
433///
434/// # Safety
435///
436/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
437/// point to a valid NUL-terminated UTF-8 string. `out_string` and `out_error`
438/// must be valid for writing when non-null. Any returned output or error string
439/// must be released with [`copperlace_string_free`].
440#[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/// Renders a named rule with initial context, inferring formatted structured JSON for object-valued rules.
461///
462/// # Safety
463///
464/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
465/// point to a valid NUL-terminated UTF-8 string. When `context_len` is nonzero,
466/// `context_keys` and `context_values` must each point to arrays with at least
467/// `context_len` entries, and every entry must point to a valid
468/// NUL-terminated UTF-8 string. `out_string` and `out_error` must be valid for
469/// writing when non-null. Any returned output or error string must be released
470/// with [`copperlace_string_free`].
471#[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/// Renders a named rule with initial context and render options, inferring formatted structured JSON for object-valued rules.
496///
497/// # Safety
498///
499/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
500/// point to a valid NUL-terminated UTF-8 string. When `context_len` is nonzero,
501/// `context_keys` and `context_values` must each point to arrays with at least
502/// `context_len` entries, and every entry must point to a valid
503/// NUL-terminated UTF-8 string. `out_string` and `out_error` must be valid for
504/// writing when non-null. Any returned output or error string must be released
505/// with [`copperlace_string_free`].
506#[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/// Renders a named structured rule from a ruleset handle as JSON text.
553///
554/// On success, writes an owned UTF-8 JSON string to `out_json` and returns
555/// [`COPPERLACE_OK`]. When `format_json` is false, the JSON is compact. When
556/// true, it is formatted with tab indentation. On failure, writes null to
557/// `out_json`, writes an owned error string to `out_error` when provided, and
558/// returns a nonzero status code. Returned output and error strings must be
559/// released with `copperlace_string_free`.
560///
561/// # Safety
562///
563/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
564/// point to a valid NUL-terminated UTF-8 string. `out_json` must be a valid
565/// writable pointer. `out_error` must be valid for writing when non-null. Any
566/// returned output or error string must be released with
567/// [`copperlace_string_free`].
568#[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/// Renders a named structured rule from a ruleset handle with initial context.
591///
592/// `context_keys` and `context_values` are parallel arrays of UTF-8 C strings.
593/// They may be null only when `context_len` is zero. Duplicate keys are allowed;
594/// later entries replace earlier entries.
595///
596/// On success, writes an owned UTF-8 JSON string to `out_json` and returns
597/// [`COPPERLACE_OK`]. When `format_json` is false, the JSON is compact. When
598/// true, it is formatted with tab indentation. On failure, writes null to
599/// `out_json`, writes an owned error string to `out_error` when provided, and
600/// returns a nonzero status code. Returned output and error strings must be
601/// released with `copperlace_string_free`.
602///
603/// # Safety
604///
605/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
606/// point to a valid NUL-terminated UTF-8 string. When `context_len` is nonzero,
607/// `context_keys` and `context_values` must each point to arrays with at least
608/// `context_len` entries, and every entry must point to a valid
609/// NUL-terminated UTF-8 string. `out_json` must be a valid writable pointer.
610/// `out_error` must be valid for writing when non-null. Any returned output or
611/// error string must be released with [`copperlace_string_free`].
612#[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/// Renders a named structured rule from a ruleset handle with initial context and render options.
639///
640/// # Safety
641///
642/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
643/// point to a valid NUL-terminated UTF-8 string. When `context_len` is nonzero,
644/// `context_keys` and `context_values` must each point to arrays with at least
645/// `context_len` entries, and every entry must point to a valid
646/// NUL-terminated UTF-8 string. `out_json` must be a valid writable pointer.
647/// `out_error` must be valid for writing when non-null. Any returned output or
648/// error string must be released with [`copperlace_string_free`].
649#[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/// Releases a ruleset handle returned by the C ABI.
787///
788/// Passing null is allowed and has no effect.
789///
790/// # Safety
791///
792/// `handle` must be null or a handle previously returned by Copperlace that has
793/// not already been freed. After this call, the handle must not be used again.
794#[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/// Releases a string returned by the C ABI.
804///
805/// Passing null is allowed and has no effect.
806///
807/// # Safety
808///
809/// `value` must be null or a string pointer previously returned by Copperlace
810/// that has not already been freed. After this call, the pointer must not be
811/// used again.
812#[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}