Skip to main content

core_invoice_sys/
lib.rs

1//! C ABI. Python and WASM bind here.
2
3use core_invoice::Profile;
4use core_invoice_formats::validate_xml;
5use std::ffi::{CStr, CString};
6use std::os::raw::{c_char, c_int};
7
8/// 0 valid, 1 invalid, 2 unreadable.
9#[unsafe(no_mangle)]
10pub extern "C" fn core_invoice_validate_ubl(
11    xml: *const c_char,
12    profile: *const c_char,
13    err: *mut c_char,
14    err_len: usize,
15) -> c_int {
16    if xml.is_null() {
17        return write_err(err, err_len, "xml is null", 2);
18    }
19    let xml = unsafe { CStr::from_ptr(xml) }.to_string_lossy();
20    let profile = if profile.is_null() {
21        Profile::Pint
22    } else {
23        let s = unsafe { CStr::from_ptr(profile) }.to_string_lossy();
24        Profile::parse(s.as_ref()).unwrap_or(Profile::Pint)
25    };
26    match validate_xml(xml.as_ref(), Some(profile)) {
27        Ok(report) if report.ok() => 0,
28        Ok(report) => write_err(err, err_len, &report.to_string(), 1),
29        Err(e) => write_err(err, err_len, &e.to_string(), 2),
30    }
31}
32
33fn write_err(err: *mut c_char, err_len: usize, msg: &str, code: c_int) -> c_int {
34    if !err.is_null() && err_len > 0 {
35        let c = CString::new(msg.chars().take(err_len.saturating_sub(1)).collect::<String>())
36            .unwrap_or_else(|_| CString::new("error").unwrap());
37        let bytes = c.as_bytes_with_nul();
38        let n = bytes.len().min(err_len);
39        unsafe {
40            std::ptr::copy_nonoverlapping(bytes.as_ptr().cast::<c_char>(), err, n);
41            *err.add(n.saturating_sub(1)) = 0;
42        }
43    }
44    code
45}