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::{ConfigError, ruleset_from_file, ruleset_from_str};
6use crate::render::{Processor, ProcessorRegistry, RenderContext, RuleSet};
7
8/// Status code for a successful C ABI call.
9pub const COPPERLACE_OK: c_int = 0;
10/// Status code for invalid C ABI arguments such as null required pointers.
11pub const COPPERLACE_INVALID_ARGUMENT: c_int = 1;
12/// Status code for config loading, parsing, or compilation failures.
13pub const COPPERLACE_PARSE_ERROR: c_int = 2;
14/// Status code for rule rendering failures.
15pub const COPPERLACE_RENDER_ERROR: c_int = 3;
16
17/// Host callback used by custom C ABI processors.
18///
19/// The callback receives a UTF-8 input string, an opaque result handle, and the
20/// user data pointer provided when creating the ruleset. It should set either
21/// output or error on `result` and return [`COPPERLACE_OK`] on success.
22pub type CopperlaceProcessorCallback =
23    unsafe extern "C" fn(*const c_char, *mut CopperlaceProcessorResult, *mut c_void) -> c_int;
24
25/// Opaque C ABI handle for a compiled Copperlace ruleset.
26///
27/// Handles are allocated by `copperlace_ruleset_from_file` or
28/// `copperlace_ruleset_from_string` and must be released with
29/// `copperlace_ruleset_free`.
30pub struct CopperlaceRuleSet {
31    ruleset: RuleSet,
32}
33
34/// Opaque C ABI result handle passed to custom processor callbacks.
35pub 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/// Loads a configuration file and returns an opaque ruleset handle.
71///
72/// On success, writes a non-null handle to `out_handle` and returns
73/// [`COPPERLACE_OK`]. On failure, writes null to `out_handle`, writes an owned
74/// error string to `out_error` when provided, and returns a nonzero status code.
75/// Returned error strings must be released with `copperlace_string_free`.
76///
77/// # Safety
78///
79/// `path` must point to a valid NUL-terminated UTF-8 string. `out_handle` must
80/// be a valid writable pointer when non-null. `out_error` must be valid for
81/// writing when non-null, and any returned error string must be released with
82/// [`copperlace_string_free`].
83#[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/// Loads a configuration file and returns a ruleset handle with custom processors.
107///
108/// # Safety
109///
110/// `path` must point to a valid NUL-terminated UTF-8 string. When
111/// `processor_len` is nonzero, `processor_names`, `processor_callbacks`, and
112/// `processor_user_data` must each point to arrays with at least
113/// `processor_len` entries. Processor names must point to valid NUL-terminated
114/// UTF-8 strings. `out_handle` must be a valid writable pointer when non-null.
115/// `out_error` must be valid for writing when non-null, and any returned error
116/// string must be released with [`copperlace_string_free`]. Processor callbacks
117/// and user data must remain valid until the returned ruleset handle is freed.
118#[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/// Compiles a configuration string and returns an opaque ruleset handle.
156///
157/// On success, writes a non-null handle to `out_handle` and returns
158/// [`COPPERLACE_OK`]. On failure, writes null to `out_handle`, writes an owned
159/// error string to `out_error` when provided, and returns a nonzero status code.
160/// Returned error strings must be released with `copperlace_string_free`.
161///
162/// # Safety
163///
164/// `config` must point to a valid NUL-terminated UTF-8 string. `out_handle`
165/// must be a valid writable pointer when non-null. `out_error` must be valid
166/// for writing when non-null, and any returned error string must be released
167/// with [`copperlace_string_free`].
168#[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/// Compiles a configuration string and returns a ruleset handle with custom processors.
192///
193/// # Safety
194///
195/// `config` must point to a valid NUL-terminated UTF-8 string. When
196/// `processor_len` is nonzero, `processor_names`, `processor_callbacks`, and
197/// `processor_user_data` must each point to arrays with at least
198/// `processor_len` entries. Processor names must point to valid NUL-terminated
199/// UTF-8 strings. `out_handle` must be a valid writable pointer when non-null.
200/// `out_error` must be valid for writing when non-null, and any returned error
201/// string must be released with [`copperlace_string_free`]. Processor callbacks
202/// and user data must remain valid until the returned ruleset handle is freed.
203#[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/// Sets the output for a custom processor callback result.
241///
242/// # Safety
243///
244/// `result` must be the valid result handle passed to the active processor
245/// callback. `value` must point to a valid NUL-terminated UTF-8 string.
246#[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/// Sets the error for a custom processor callback result.
264///
265/// # Safety
266///
267/// `result` must be the valid result handle passed to the active processor
268/// callback. `message` must point to a valid NUL-terminated UTF-8 string.
269#[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/// Renders a named rule from a ruleset handle.
287///
288/// On success, writes an owned UTF-8 string to `out_string` and returns
289/// [`COPPERLACE_OK`]. On failure, writes null to `out_string`, writes an owned
290/// error string to `out_error` when provided, and returns a nonzero status code.
291/// Returned output and error strings must be released with
292/// `copperlace_string_free`.
293///
294/// # Safety
295///
296/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
297/// point to a valid NUL-terminated UTF-8 string. `out_string` and `out_error`
298/// must be valid for writing when non-null. Any returned output or error string
299/// must be released with [`copperlace_string_free`].
300#[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/// Renders a named rule from a ruleset handle with initial context values.
321///
322/// `context_keys` and `context_values` are parallel arrays of UTF-8 C strings.
323/// They may be null only when `context_len` is zero. Duplicate keys are allowed;
324/// later entries replace earlier entries.
325///
326/// On success, writes an owned UTF-8 string to `out_string` and returns
327/// [`COPPERLACE_OK`]. On failure, writes null to `out_string`, writes an owned
328/// error string to `out_error` when provided, and returns a nonzero status code.
329/// Returned output and error strings must be released with
330/// `copperlace_string_free`.
331///
332/// # Safety
333///
334/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
335/// point to a valid NUL-terminated UTF-8 string. When `context_len` is nonzero,
336/// `context_keys` and `context_values` must each point to arrays with at least
337/// `context_len` entries, and every entry must point to a valid
338/// NUL-terminated UTF-8 string. `out_string` and `out_error` must be valid for
339/// writing when non-null. Any returned output or error string must be released
340/// with [`copperlace_string_free`].
341#[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/// Renders a named rule, inferring formatted structured JSON for object-valued rules.
384///
385/// String-valued and list-valued rules use existing text rendering. Object-valued
386/// rules return formatted JSON using tab indentation.
387///
388/// # Safety
389///
390/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
391/// point to a valid NUL-terminated UTF-8 string. `out_string` and `out_error`
392/// must be valid for writing when non-null. Any returned output or error string
393/// must be released with [`copperlace_string_free`].
394#[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/// Renders a named rule with initial context, inferring formatted structured JSON for object-valued rules.
415///
416/// # Safety
417///
418/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
419/// point to a valid NUL-terminated UTF-8 string. When `context_len` is nonzero,
420/// `context_keys` and `context_values` must each point to arrays with at least
421/// `context_len` entries, and every entry must point to a valid
422/// NUL-terminated UTF-8 string. `out_string` and `out_error` must be valid for
423/// writing when non-null. Any returned output or error string must be released
424/// with [`copperlace_string_free`].
425#[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/// Renders a named structured rule from a ruleset handle as JSON text.
468///
469/// On success, writes an owned UTF-8 JSON string to `out_json` and returns
470/// [`COPPERLACE_OK`]. When `format_json` is false, the JSON is compact. When
471/// true, it is formatted with tab indentation. On failure, writes null to
472/// `out_json`, writes an owned error string to `out_error` when provided, and
473/// returns a nonzero status code. Returned output and error strings must be
474/// released with `copperlace_string_free`.
475///
476/// # Safety
477///
478/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
479/// point to a valid NUL-terminated UTF-8 string. `out_json` must be a valid
480/// writable pointer. `out_error` must be valid for writing when non-null. Any
481/// returned output or error string must be released with
482/// [`copperlace_string_free`].
483#[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/// Renders a named structured rule from a ruleset handle with initial context.
506///
507/// `context_keys` and `context_values` are parallel arrays of UTF-8 C strings.
508/// They may be null only when `context_len` is zero. Duplicate keys are allowed;
509/// later entries replace earlier entries.
510///
511/// On success, writes an owned UTF-8 JSON string to `out_json` and returns
512/// [`COPPERLACE_OK`]. When `format_json` is false, the JSON is compact. When
513/// true, it is formatted with tab indentation. On failure, writes null to
514/// `out_json`, writes an owned error string to `out_error` when provided, and
515/// returns a nonzero status code. Returned output and error strings must be
516/// released with `copperlace_string_free`.
517///
518/// # Safety
519///
520/// `handle` must be a live ruleset handle returned by Copperlace. `rule` must
521/// point to a valid NUL-terminated UTF-8 string. When `context_len` is nonzero,
522/// `context_keys` and `context_values` must each point to arrays with at least
523/// `context_len` entries, and every entry must point to a valid
524/// NUL-terminated UTF-8 string. `out_json` must be a valid writable pointer.
525/// `out_error` must be valid for writing when non-null. Any returned output or
526/// error string must be released with [`copperlace_string_free`].
527#[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/// Releases a ruleset handle returned by the C ABI.
679///
680/// Passing null is allowed and has no effect.
681///
682/// # Safety
683///
684/// `handle` must be null or a handle previously returned by Copperlace that has
685/// not already been freed. After this call, the handle must not be used again.
686#[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/// Releases a string returned by the C ABI.
696///
697/// Passing null is allowed and has no effect.
698///
699/// # Safety
700///
701/// `value` must be null or a string pointer previously returned by Copperlace
702/// that has not already been freed. After this call, the pointer must not be
703/// used again.
704#[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}