Skip to main content

polyxml/
lib.rs

1#![allow(clippy::missing_safety_doc)]
2
3use std::ffi::CStr;
4use std::os::raw::c_char;
5use std::sync::Arc;
6
7use polyxml::schema::{
8    FieldKind, FieldSchema, ModelSchema, ModelSchemaBuilder, ScalarType, ValueType,
9};
10use polyxml::value::PolyValue;
11
12// Opaque types
13pub struct PolyXmlSchemaBuilder(ModelSchemaBuilder);
14pub struct PolyXmlSchema(Arc<ModelSchema>);
15pub struct PolyXmlValue(PolyValue);
16
17#[repr(C)]
18pub enum PolyXmlFieldKind {
19    Attribute = 0,
20    Element = 1,
21    Text = 2,
22}
23
24#[repr(C)]
25pub enum PolyXmlScalarType {
26    String = 0,
27    Int = 1,
28    Float = 2,
29    Bool = 3,
30    Decimal = 4,
31    XmlDate = 5,
32    XmlDateTime = 6,
33    Any = 7,
34}
35
36#[repr(C)]
37pub enum PolyXmlErrorCode {
38    Ok = 0,
39    ErrSyntax = 1,
40    ErrScalar = 2,
41    ErrSchema = 3,
42    ErrNullPtr = 4,
43    ErrUtf8 = 5,
44}
45
46#[no_mangle]
47pub unsafe extern "C" fn polyxml_schema_builder_create(
48    name: *const c_char,
49) -> *mut PolyXmlSchemaBuilder {
50    if name.is_null() {
51        return std::ptr::null_mut();
52    }
53    let name_str = match CStr::from_ptr(name).to_str() {
54        Ok(s) => s,
55        Err(_) => return std::ptr::null_mut(),
56    };
57    Box::into_raw(Box::new(PolyXmlSchemaBuilder(ModelSchema::builder(
58        name_str,
59    ))))
60}
61
62#[no_mangle]
63pub unsafe extern "C" fn polyxml_schema_builder_set_namespace(
64    builder: *mut PolyXmlSchemaBuilder,
65    namespace_uri: *const c_char,
66) {
67    if builder.is_null() || namespace_uri.is_null() {
68        return;
69    }
70    let ns_str = match CStr::from_ptr(namespace_uri).to_str() {
71        Ok(s) => s,
72        Err(_) => return,
73    };
74    let b = &mut *builder;
75    b.0 = std::mem::replace(&mut b.0, ModelSchema::builder("")).with_namespace(ns_str);
76}
77
78#[no_mangle]
79pub unsafe extern "C" fn polyxml_schema_builder_add_field(
80    builder: *mut PolyXmlSchemaBuilder,
81    name: *const c_char,
82    xml_name: *const c_char,
83    kind: PolyXmlFieldKind,
84    scalar_type: PolyXmlScalarType,
85) {
86    polyxml_schema_builder_add_field_with_namespace(
87        builder,
88        name,
89        xml_name,
90        kind,
91        scalar_type,
92        std::ptr::null(),
93    );
94}
95
96#[no_mangle]
97pub unsafe extern "C" fn polyxml_schema_builder_add_field_with_namespace(
98    builder: *mut PolyXmlSchemaBuilder,
99    name: *const c_char,
100    xml_name: *const c_char,
101    kind: PolyXmlFieldKind,
102    scalar_type: PolyXmlScalarType,
103    namespace_uri: *const c_char,
104) {
105    if builder.is_null() || name.is_null() || xml_name.is_null() {
106        return;
107    }
108    let name_str = match CStr::from_ptr(name).to_str() {
109        Ok(s) => s,
110        Err(_) => return,
111    };
112    let xml_bytes = CStr::from_ptr(xml_name).to_bytes();
113
114    let field_kind = match kind {
115        PolyXmlFieldKind::Attribute => FieldKind::Attribute,
116        PolyXmlFieldKind::Element => FieldKind::Element,
117        PolyXmlFieldKind::Text => FieldKind::Text,
118    };
119
120    let sc_type = match scalar_type {
121        PolyXmlScalarType::String => ScalarType::String,
122        PolyXmlScalarType::Int => ScalarType::Int,
123        PolyXmlScalarType::Float => ScalarType::Float,
124        PolyXmlScalarType::Bool => ScalarType::Bool,
125        PolyXmlScalarType::Decimal => ScalarType::Decimal,
126        PolyXmlScalarType::XmlDate => ScalarType::XmlDate,
127        PolyXmlScalarType::XmlDateTime => ScalarType::XmlDateTime,
128        PolyXmlScalarType::Any => ScalarType::Any,
129    };
130
131    let mut field = FieldSchema::new(name_str, xml_bytes, field_kind, ValueType::Scalar(sc_type));
132    if !namespace_uri.is_null() {
133        if let Ok(ns_str) = CStr::from_ptr(namespace_uri).to_str() {
134            field = field.with_namespace(ns_str);
135        }
136    }
137    let b = &mut *builder;
138    b.0 = std::mem::replace(&mut b.0, ModelSchema::builder("")).field(field);
139}
140
141#[no_mangle]
142pub unsafe extern "C" fn polyxml_schema_builder_build(
143    builder: *mut PolyXmlSchemaBuilder,
144) -> *mut PolyXmlSchema {
145    if builder.is_null() {
146        return std::ptr::null_mut();
147    }
148    let boxed = Box::from_raw(builder);
149    let schema = boxed.0.build();
150    Box::into_raw(Box::new(PolyXmlSchema(schema)))
151}
152
153#[no_mangle]
154pub unsafe extern "C" fn polyxml_schema_free(schema: *mut PolyXmlSchema) {
155    if !schema.is_null() {
156        drop(Box::from_raw(schema));
157    }
158}
159
160#[no_mangle]
161pub unsafe extern "C" fn polyxml_deserialize(
162    data: *const u8,
163    len: usize,
164    schema: *const PolyXmlSchema,
165    out_value: *mut *mut PolyXmlValue,
166) -> PolyXmlErrorCode {
167    if data.is_null() || schema.is_null() || out_value.is_null() {
168        return PolyXmlErrorCode::ErrNullPtr;
169    }
170
171    let slice = std::slice::from_raw_parts(data, len);
172    let s = &(*schema).0;
173
174    match polyxml::deserialize(slice, Arc::clone(s)) {
175        Ok(val) => {
176            *out_value = Box::into_raw(Box::new(PolyXmlValue(val)));
177            PolyXmlErrorCode::Ok
178        }
179        Err(polyxml::error::PolyXmlError::XmlSyntaxError { .. }) => PolyXmlErrorCode::ErrSyntax,
180        Err(polyxml::error::PolyXmlError::ScalarParseError { .. }) => PolyXmlErrorCode::ErrScalar,
181        Err(_) => PolyXmlErrorCode::ErrSchema,
182    }
183}
184
185#[no_mangle]
186pub unsafe extern "C" fn polyxml_serialize(
187    root_name: *const c_char,
188    value: *const PolyXmlValue,
189    schema: *const PolyXmlSchema,
190    indent: i32,
191    out_bytes: *mut *mut u8,
192    out_len: *mut usize,
193) -> PolyXmlErrorCode {
194    polyxml_serialize_with_options(
195        root_name,
196        value,
197        schema,
198        indent,
199        -1,
200        std::ptr::null(),
201        std::ptr::null(),
202        0,
203        out_bytes,
204        out_len,
205    )
206}
207
208#[no_mangle]
209pub unsafe extern "C" fn polyxml_serialize_with_options(
210    root_name: *const c_char,
211    value: *const PolyXmlValue,
212    schema: *const PolyXmlSchema,
213    indent: i32,
214    enable_namespaces: i32,
215    ns_prefixes: *const *const c_char,
216    ns_uris: *const *const c_char,
217    ns_count: usize,
218    out_bytes: *mut *mut u8,
219    out_len: *mut usize,
220) -> PolyXmlErrorCode {
221    if root_name.is_null()
222        || value.is_null()
223        || schema.is_null()
224        || out_bytes.is_null()
225        || out_len.is_null()
226    {
227        return PolyXmlErrorCode::ErrNullPtr;
228    }
229
230    let root_str = match CStr::from_ptr(root_name).to_str() {
231        Ok(s) => s,
232        Err(_) => return PolyXmlErrorCode::ErrUtf8,
233    };
234
235    let indent_opt = if indent > 0 {
236        Some(indent as usize)
237    } else {
238        None
239    };
240
241    let enable_ns_opt = match enable_namespaces {
242        0 => Some(false),
243        1 => Some(true),
244        _ => None,
245    };
246
247    let ns_map = if ns_count > 0 && !ns_prefixes.is_null() && !ns_uris.is_null() {
248        let mut map = std::collections::HashMap::new();
249        let prefix_slice = std::slice::from_raw_parts(ns_prefixes, ns_count);
250        let uri_slice = std::slice::from_raw_parts(ns_uris, ns_count);
251        for i in 0..ns_count {
252            let prefix_ptr = prefix_slice[i];
253            let uri_ptr = uri_slice[i];
254            if !uri_ptr.is_null() {
255                if let Ok(uri) = CStr::from_ptr(uri_ptr).to_str() {
256                    let prefix = if prefix_ptr.is_null() {
257                        String::new()
258                    } else if let Ok(p) = CStr::from_ptr(prefix_ptr).to_str() {
259                        p.to_string()
260                    } else {
261                        String::new()
262                    };
263                    map.insert(prefix, uri.to_string());
264                }
265            }
266        }
267        Some(map)
268    } else {
269        None
270    };
271
272    let val = &(*value).0;
273    let s = &(*schema).0;
274
275    match polyxml::serialize_with_options(
276        root_str,
277        val,
278        s,
279        indent_opt,
280        enable_ns_opt,
281        ns_map.as_ref(),
282    ) {
283        Ok(mut bytes) => {
284            bytes.shrink_to_fit();
285            *out_len = bytes.len();
286            *out_bytes = bytes.as_mut_ptr();
287            std::mem::forget(bytes);
288            PolyXmlErrorCode::Ok
289        }
290        Err(_) => PolyXmlErrorCode::ErrSchema,
291    }
292}
293
294#[no_mangle]
295pub unsafe extern "C" fn polyxml_bytes_free(bytes: *mut u8, len: usize) {
296    if !bytes.is_null() {
297        drop(Vec::from_raw_parts(bytes, len, len));
298    }
299}
300
301#[no_mangle]
302pub unsafe extern "C" fn polyxml_value_get_field(
303    val: *const PolyXmlValue,
304    key: *const c_char,
305) -> *const PolyXmlValue {
306    if val.is_null() || key.is_null() {
307        return std::ptr::null();
308    }
309    let key_str = match CStr::from_ptr(key).to_str() {
310        Ok(s) => s,
311        Err(_) => return std::ptr::null(),
312    };
313    match &(*val).0 {
314        PolyValue::Object(map) => {
315            if let Some(sub_val) = map.get(key_str) {
316                // Return borrowed reference as pointer
317                sub_val as *const PolyValue as *const PolyXmlValue
318            } else {
319                std::ptr::null()
320            }
321        }
322        _ => std::ptr::null(),
323    }
324}
325
326#[no_mangle]
327pub unsafe extern "C" fn polyxml_value_get_int(
328    val: *const PolyXmlValue,
329    out_int: *mut i64,
330) -> PolyXmlErrorCode {
331    if val.is_null() || out_int.is_null() {
332        return PolyXmlErrorCode::ErrNullPtr;
333    }
334    if let Some(i) = (*val).0.as_i64() {
335        *out_int = i;
336        PolyXmlErrorCode::Ok
337    } else {
338        PolyXmlErrorCode::ErrScalar
339    }
340}
341
342#[no_mangle]
343pub unsafe extern "C" fn polyxml_value_get_float(
344    val: *const PolyXmlValue,
345    out_float: *mut f64,
346) -> PolyXmlErrorCode {
347    if val.is_null() || out_float.is_null() {
348        return PolyXmlErrorCode::ErrNullPtr;
349    }
350    if let Some(f) = (*val).0.as_f64() {
351        *out_float = f;
352        PolyXmlErrorCode::Ok
353    } else {
354        PolyXmlErrorCode::ErrScalar
355    }
356}
357
358#[no_mangle]
359pub unsafe extern "C" fn polyxml_value_get_bool(
360    val: *const PolyXmlValue,
361    out_bool: *mut bool,
362) -> PolyXmlErrorCode {
363    if val.is_null() || out_bool.is_null() {
364        return PolyXmlErrorCode::ErrNullPtr;
365    }
366    if let Some(b) = (*val).0.as_bool() {
367        *out_bool = b;
368        PolyXmlErrorCode::Ok
369    } else {
370        PolyXmlErrorCode::ErrScalar
371    }
372}
373
374#[no_mangle]
375pub unsafe extern "C" fn polyxml_value_get_string(
376    val: *const PolyXmlValue,
377    out_str: *mut *const c_char,
378    out_len: *mut usize,
379) -> PolyXmlErrorCode {
380    if val.is_null() || out_str.is_null() || out_len.is_null() {
381        return PolyXmlErrorCode::ErrNullPtr;
382    }
383    if let Some(s) = (*val).0.as_str() {
384        *out_str = s.as_ptr() as *const c_char;
385        *out_len = s.len();
386        PolyXmlErrorCode::Ok
387    } else {
388        PolyXmlErrorCode::ErrScalar
389    }
390}
391
392#[no_mangle]
393pub unsafe extern "C" fn polyxml_value_get_list_len(
394    val: *const PolyXmlValue,
395    out_len: *mut usize,
396) -> PolyXmlErrorCode {
397    if val.is_null() || out_len.is_null() {
398        return PolyXmlErrorCode::ErrNullPtr;
399    }
400    if let Some(l) = (*val).0.as_list() {
401        *out_len = l.len();
402        PolyXmlErrorCode::Ok
403    } else {
404        PolyXmlErrorCode::ErrScalar
405    }
406}
407
408#[no_mangle]
409pub unsafe extern "C" fn polyxml_value_get_list_item(
410    val: *const PolyXmlValue,
411    idx: usize,
412) -> *const PolyXmlValue {
413    if val.is_null() {
414        return std::ptr::null();
415    }
416    if let Some(l) = (*val).0.as_list() {
417        if let Some(item) = l.get(idx) {
418            return item as *const PolyValue as *const PolyXmlValue;
419        }
420    }
421    std::ptr::null()
422}
423
424#[no_mangle]
425pub unsafe extern "C" fn polyxml_value_is_null(val: *const PolyXmlValue) -> bool {
426    if val.is_null() {
427        return true;
428    }
429    (*val).0.is_null()
430}
431
432#[no_mangle]
433pub unsafe extern "C" fn polyxml_value_free(val: *mut PolyXmlValue) {
434    if !val.is_null() {
435        drop(Box::from_raw(val));
436    }
437}
438
439#[no_mangle]
440pub extern "C" fn polyxml_version() -> *const c_char {
441    static VERSION: &[u8] = concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes();
442    VERSION.as_ptr() as *const c_char
443}
444
445#[cfg(test)]
446mod tests {
447    use super::*;
448    use std::ffi::CString;
449
450    #[test]
451    fn test_c_abi_namespaced_roundtrip() {
452        unsafe {
453            let name = CString::new("Order").unwrap();
454            let ns_order = CString::new("https://example.com/orders").unwrap();
455            let builder = polyxml_schema_builder_create(name.as_ptr());
456            polyxml_schema_builder_set_namespace(builder, ns_order.as_ptr());
457
458            let id_name = CString::new("id").unwrap();
459            let id_xml = CString::new("id").unwrap();
460            polyxml_schema_builder_add_field(
461                builder,
462                id_name.as_ptr(),
463                id_xml.as_ptr(),
464                PolyXmlFieldKind::Attribute,
465                PolyXmlScalarType::Int,
466            );
467
468            let item_name = CString::new("item").unwrap();
469            let item_xml = CString::new("item").unwrap();
470            let ns_item = CString::new("https://example.com/items").unwrap();
471            polyxml_schema_builder_add_field_with_namespace(
472                builder,
473                item_name.as_ptr(),
474                item_xml.as_ptr(),
475                PolyXmlFieldKind::Element,
476                PolyXmlScalarType::String,
477                ns_item.as_ptr(),
478            );
479
480            let schema = polyxml_schema_builder_build(builder);
481            assert!(!schema.is_null());
482
483            let xml = br#"<ns0:Order xmlns:ns0="https://example.com/orders" xmlns:ns1="https://example.com/items" id="101"><ns1:item>Gadget</ns1:item></ns0:Order>"#;
484            let mut val_ptr: *mut PolyXmlValue = std::ptr::null_mut();
485            let err = polyxml_deserialize(xml.as_ptr(), xml.len(), schema, &mut val_ptr);
486            assert_eq!(err as i32, PolyXmlErrorCode::Ok as i32);
487            assert!(!val_ptr.is_null());
488
489            let mut int_val = 0;
490            let id_field = polyxml_value_get_field(val_ptr, id_name.as_ptr());
491            assert!(!id_field.is_null());
492            polyxml_value_get_int(id_field, &mut int_val);
493            assert_eq!(int_val, 101);
494
495            let mut out_bytes: *mut u8 = std::ptr::null_mut();
496            let mut out_len = 0;
497            let p0 = CString::new("ord").unwrap();
498            let u0 = CString::new("https://example.com/orders").unwrap();
499            let prefixes = [p0.as_ptr()];
500            let uris = [u0.as_ptr()];
501
502            let serr = polyxml_serialize_with_options(
503                name.as_ptr(),
504                val_ptr,
505                schema,
506                0,
507                1,
508                prefixes.as_ptr(),
509                uris.as_ptr(),
510                1,
511                &mut out_bytes,
512                &mut out_len,
513            );
514            assert_eq!(serr as i32, PolyXmlErrorCode::Ok as i32);
515            assert!(!out_bytes.is_null());
516            let s = std::str::from_utf8(std::slice::from_raw_parts(out_bytes, out_len)).unwrap();
517            assert!(s.contains("xmlns:ord=\"https://example.com/orders\""));
518            assert!(s.contains("<ord:Order"));
519            assert!(s.contains("id=\"101\""));
520
521            polyxml_bytes_free(out_bytes, out_len);
522            polyxml_value_free(val_ptr);
523            polyxml_schema_free(schema);
524        }
525    }
526}