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_add_nested_field(
143    builder: *mut PolyXmlSchemaBuilder,
144    name: *const c_char,
145    xml_name: *const c_char,
146    nested_schema: *const PolyXmlSchema,
147    namespace_uri: *const c_char,
148) {
149    if builder.is_null() || name.is_null() || xml_name.is_null() || nested_schema.is_null() {
150        return;
151    }
152    let name_str = match CStr::from_ptr(name).to_str() {
153        Ok(s) => s,
154        Err(_) => return,
155    };
156    let xml_bytes = CStr::from_ptr(xml_name).to_bytes();
157    let nested = Arc::clone(&(*nested_schema).0);
158
159    let mut field = FieldSchema::new(
160        name_str,
161        xml_bytes,
162        FieldKind::Element,
163        ValueType::Nested(nested),
164    );
165    if !namespace_uri.is_null() {
166        if let Ok(ns_str) = CStr::from_ptr(namespace_uri).to_str() {
167            field = field.with_namespace(ns_str);
168        }
169    }
170    let b = &mut *builder;
171    b.0 = std::mem::replace(&mut b.0, ModelSchema::builder("")).field(field);
172}
173
174#[no_mangle]
175pub unsafe extern "C" fn polyxml_schema_builder_add_list_nested_field(
176    builder: *mut PolyXmlSchemaBuilder,
177    name: *const c_char,
178    xml_name: *const c_char,
179    item_schema: *const PolyXmlSchema,
180    namespace_uri: *const c_char,
181) {
182    if builder.is_null() || name.is_null() || xml_name.is_null() || item_schema.is_null() {
183        return;
184    }
185    let name_str = match CStr::from_ptr(name).to_str() {
186        Ok(s) => s,
187        Err(_) => return,
188    };
189    let xml_bytes = CStr::from_ptr(xml_name).to_bytes();
190    let item = Arc::clone(&(*item_schema).0);
191
192    let mut field = FieldSchema::new(
193        name_str,
194        xml_bytes,
195        FieldKind::Element,
196        ValueType::List(Box::new(ValueType::Nested(item))),
197    );
198    if !namespace_uri.is_null() {
199        if let Ok(ns_str) = CStr::from_ptr(namespace_uri).to_str() {
200            field = field.with_namespace(ns_str);
201        }
202    }
203    let b = &mut *builder;
204    b.0 = std::mem::replace(&mut b.0, ModelSchema::builder("")).field(field);
205}
206
207#[no_mangle]
208pub unsafe extern "C" fn polyxml_schema_builder_add_list_scalar_field(
209    builder: *mut PolyXmlSchemaBuilder,
210    name: *const c_char,
211    xml_name: *const c_char,
212    scalar_type: PolyXmlScalarType,
213    namespace_uri: *const c_char,
214) {
215    if builder.is_null() || name.is_null() || xml_name.is_null() {
216        return;
217    }
218    let name_str = match CStr::from_ptr(name).to_str() {
219        Ok(s) => s,
220        Err(_) => return,
221    };
222    let xml_bytes = CStr::from_ptr(xml_name).to_bytes();
223    let sc_type = match scalar_type {
224        PolyXmlScalarType::String => ScalarType::String,
225        PolyXmlScalarType::Int => ScalarType::Int,
226        PolyXmlScalarType::Float => ScalarType::Float,
227        PolyXmlScalarType::Bool => ScalarType::Bool,
228        PolyXmlScalarType::Decimal => ScalarType::Decimal,
229        PolyXmlScalarType::XmlDate => ScalarType::XmlDate,
230        PolyXmlScalarType::XmlDateTime => ScalarType::XmlDateTime,
231        PolyXmlScalarType::Any => ScalarType::Any,
232    };
233
234    let mut field = FieldSchema::new(
235        name_str,
236        xml_bytes,
237        FieldKind::Element,
238        ValueType::List(Box::new(ValueType::Scalar(sc_type))),
239    );
240    if !namespace_uri.is_null() {
241        if let Ok(ns_str) = CStr::from_ptr(namespace_uri).to_str() {
242            field = field.with_namespace(ns_str);
243        }
244    }
245    let b = &mut *builder;
246    b.0 = std::mem::replace(&mut b.0, ModelSchema::builder("")).field(field);
247}
248
249#[no_mangle]
250pub unsafe extern "C" fn polyxml_schema_builder_build(
251    builder: *mut PolyXmlSchemaBuilder,
252) -> *mut PolyXmlSchema {
253    if builder.is_null() {
254        return std::ptr::null_mut();
255    }
256    let boxed = Box::from_raw(builder);
257    let schema = boxed.0.build();
258    Box::into_raw(Box::new(PolyXmlSchema(schema)))
259}
260
261#[no_mangle]
262pub unsafe extern "C" fn polyxml_schema_free(schema: *mut PolyXmlSchema) {
263    if !schema.is_null() {
264        drop(Box::from_raw(schema));
265    }
266}
267
268#[no_mangle]
269pub unsafe extern "C" fn polyxml_deserialize(
270    data: *const u8,
271    len: usize,
272    schema: *const PolyXmlSchema,
273    out_value: *mut *mut PolyXmlValue,
274) -> PolyXmlErrorCode {
275    if data.is_null() || schema.is_null() || out_value.is_null() {
276        return PolyXmlErrorCode::ErrNullPtr;
277    }
278
279    let slice = std::slice::from_raw_parts(data, len);
280    let s = &(*schema).0;
281
282    match polyxml::deserialize(slice, Arc::clone(s)) {
283        Ok(val) => {
284            *out_value = Box::into_raw(Box::new(PolyXmlValue(val)));
285            PolyXmlErrorCode::Ok
286        }
287        Err(polyxml::error::PolyXmlError::XmlSyntaxError { .. }) => PolyXmlErrorCode::ErrSyntax,
288        Err(polyxml::error::PolyXmlError::ScalarParseError { .. }) => PolyXmlErrorCode::ErrScalar,
289        Err(_) => PolyXmlErrorCode::ErrSchema,
290    }
291}
292
293#[no_mangle]
294pub unsafe extern "C" fn polyxml_serialize(
295    root_name: *const c_char,
296    value: *const PolyXmlValue,
297    schema: *const PolyXmlSchema,
298    indent: i32,
299    out_bytes: *mut *mut u8,
300    out_len: *mut usize,
301) -> PolyXmlErrorCode {
302    polyxml_serialize_with_options(
303        root_name,
304        value,
305        schema,
306        indent,
307        -1,
308        std::ptr::null(),
309        std::ptr::null(),
310        0,
311        out_bytes,
312        out_len,
313    )
314}
315
316#[no_mangle]
317pub unsafe extern "C" fn polyxml_serialize_with_options(
318    root_name: *const c_char,
319    value: *const PolyXmlValue,
320    schema: *const PolyXmlSchema,
321    indent: i32,
322    enable_namespaces: i32,
323    ns_prefixes: *const *const c_char,
324    ns_uris: *const *const c_char,
325    ns_count: usize,
326    out_bytes: *mut *mut u8,
327    out_len: *mut usize,
328) -> PolyXmlErrorCode {
329    if root_name.is_null()
330        || value.is_null()
331        || schema.is_null()
332        || out_bytes.is_null()
333        || out_len.is_null()
334    {
335        return PolyXmlErrorCode::ErrNullPtr;
336    }
337
338    let root_str = match CStr::from_ptr(root_name).to_str() {
339        Ok(s) => s,
340        Err(_) => return PolyXmlErrorCode::ErrUtf8,
341    };
342
343    let indent_opt = if indent > 0 {
344        Some(indent as usize)
345    } else {
346        None
347    };
348
349    let enable_ns_opt = match enable_namespaces {
350        0 => Some(false),
351        1 => Some(true),
352        _ => None,
353    };
354
355    let ns_map = if ns_count > 0 && !ns_prefixes.is_null() && !ns_uris.is_null() {
356        let mut map = std::collections::HashMap::new();
357        let prefix_slice = std::slice::from_raw_parts(ns_prefixes, ns_count);
358        let uri_slice = std::slice::from_raw_parts(ns_uris, ns_count);
359        for i in 0..ns_count {
360            let prefix_ptr = prefix_slice[i];
361            let uri_ptr = uri_slice[i];
362            if !uri_ptr.is_null() {
363                if let Ok(uri) = CStr::from_ptr(uri_ptr).to_str() {
364                    let prefix = if prefix_ptr.is_null() {
365                        String::new()
366                    } else if let Ok(p) = CStr::from_ptr(prefix_ptr).to_str() {
367                        p.to_string()
368                    } else {
369                        String::new()
370                    };
371                    map.insert(prefix, uri.to_string());
372                }
373            }
374        }
375        Some(map)
376    } else {
377        None
378    };
379
380    let val = &(*value).0;
381    let s = &(*schema).0;
382
383    match polyxml::serialize_with_options(
384        root_str,
385        val,
386        s,
387        indent_opt,
388        enable_ns_opt,
389        ns_map.as_ref(),
390    ) {
391        Ok(mut bytes) => {
392            bytes.shrink_to_fit();
393            *out_len = bytes.len();
394            *out_bytes = bytes.as_mut_ptr();
395            std::mem::forget(bytes);
396            PolyXmlErrorCode::Ok
397        }
398        Err(_) => PolyXmlErrorCode::ErrSchema,
399    }
400}
401
402#[no_mangle]
403pub unsafe extern "C" fn polyxml_bytes_free(bytes: *mut u8, len: usize) {
404    if !bytes.is_null() {
405        drop(Vec::from_raw_parts(bytes, len, len));
406    }
407}
408
409#[no_mangle]
410pub unsafe extern "C" fn polyxml_value_create_record(
411    schema: *const PolyXmlSchema,
412) -> *mut PolyXmlValue {
413    if schema.is_null() {
414        return std::ptr::null_mut();
415    }
416    let s = Arc::clone(&(*schema).0);
417    let count = s.fields.len();
418    let val = PolyValue::Record {
419        schema: s,
420        values: vec![None; count].into_boxed_slice(),
421    };
422    Box::into_raw(Box::new(PolyXmlValue(val)))
423}
424
425#[no_mangle]
426pub unsafe extern "C" fn polyxml_value_create_list() -> *mut PolyXmlValue {
427    Box::into_raw(Box::new(PolyXmlValue(PolyValue::List(Vec::new()))))
428}
429
430#[no_mangle]
431pub unsafe extern "C" fn polyxml_value_create_list_with_capacity(
432    capacity: usize,
433) -> *mut PolyXmlValue {
434    Box::into_raw(Box::new(PolyXmlValue(PolyValue::List(Vec::with_capacity(
435        capacity,
436    )))))
437}
438
439#[no_mangle]
440pub unsafe extern "C" fn polyxml_value_create_string(
441    str_ptr: *const c_char,
442    len: usize,
443) -> *mut PolyXmlValue {
444    if str_ptr.is_null() {
445        return std::ptr::null_mut();
446    }
447    let slice = std::slice::from_raw_parts(str_ptr as *const u8, len);
448    let s = match std::str::from_utf8(slice) {
449        Ok(valid) => valid.to_string(),
450        Err(_) => return std::ptr::null_mut(),
451    };
452    Box::into_raw(Box::new(PolyXmlValue(PolyValue::String(s))))
453}
454
455#[no_mangle]
456pub unsafe extern "C" fn polyxml_value_create_int(val: i64) -> *mut PolyXmlValue {
457    Box::into_raw(Box::new(PolyXmlValue(PolyValue::Int(val))))
458}
459
460#[no_mangle]
461pub unsafe extern "C" fn polyxml_value_create_float(val: f64) -> *mut PolyXmlValue {
462    Box::into_raw(Box::new(PolyXmlValue(PolyValue::Float(val))))
463}
464
465#[no_mangle]
466pub unsafe extern "C" fn polyxml_value_create_bool(val: bool) -> *mut PolyXmlValue {
467    Box::into_raw(Box::new(PolyXmlValue(PolyValue::Bool(val))))
468}
469
470#[no_mangle]
471pub unsafe extern "C" fn polyxml_value_create_null() -> *mut PolyXmlValue {
472    Box::into_raw(Box::new(PolyXmlValue(PolyValue::Null)))
473}
474
475#[no_mangle]
476pub unsafe extern "C" fn polyxml_value_clone(val: *const PolyXmlValue) -> *mut PolyXmlValue {
477    if val.is_null() {
478        return std::ptr::null_mut();
479    }
480    Box::into_raw(Box::new(PolyXmlValue((*val).0.clone())))
481}
482
483#[no_mangle]
484pub unsafe extern "C" fn polyxml_value_set_field(
485    record: *mut PolyXmlValue,
486    key: *const c_char,
487    child_val: *mut PolyXmlValue,
488) -> PolyXmlErrorCode {
489    if record.is_null() || key.is_null() || child_val.is_null() {
490        return PolyXmlErrorCode::ErrNullPtr;
491    }
492    let key_str = match CStr::from_ptr(key).to_str() {
493        Ok(s) => s,
494        Err(_) => return PolyXmlErrorCode::ErrUtf8,
495    };
496    let child_boxed = Box::from_raw(child_val);
497    let child_inner = child_boxed.0;
498
499    match &mut (*record).0 {
500        PolyValue::Record { schema, values } => {
501            if let Some(idx) = schema.fields.iter().position(|f| f.name == key_str) {
502                values[idx] = Some(child_inner);
503                PolyXmlErrorCode::Ok
504            } else {
505                PolyXmlErrorCode::ErrSchema
506            }
507        }
508        PolyValue::Object(map) => {
509            map.insert(key_str.to_string(), child_inner);
510            PolyXmlErrorCode::Ok
511        }
512        _ => PolyXmlErrorCode::ErrSchema,
513    }
514}
515
516#[no_mangle]
517pub unsafe extern "C" fn polyxml_value_list_append(
518    list: *mut PolyXmlValue,
519    item: *mut PolyXmlValue,
520) -> PolyXmlErrorCode {
521    if list.is_null() || item.is_null() {
522        return PolyXmlErrorCode::ErrNullPtr;
523    }
524    let item_boxed = Box::from_raw(item);
525    let item_inner = item_boxed.0;
526
527    match &mut (*list).0 {
528        PolyValue::List(vec) => {
529            vec.push(item_inner);
530            PolyXmlErrorCode::Ok
531        }
532        _ => PolyXmlErrorCode::ErrSchema,
533    }
534}
535
536#[no_mangle]
537pub unsafe extern "C" fn polyxml_value_get_field(
538    val: *const PolyXmlValue,
539    key: *const c_char,
540) -> *const PolyXmlValue {
541    if val.is_null() || key.is_null() {
542        return std::ptr::null();
543    }
544    let key_str = match CStr::from_ptr(key).to_str() {
545        Ok(s) => s,
546        Err(_) => return std::ptr::null(),
547    };
548    if let Some(sub_val) = (*val).0.get(key_str) {
549        sub_val as *const PolyValue as *const PolyXmlValue
550    } else {
551        std::ptr::null()
552    }
553}
554
555#[no_mangle]
556pub unsafe extern "C" fn polyxml_value_get_int(
557    val: *const PolyXmlValue,
558    out_int: *mut i64,
559) -> PolyXmlErrorCode {
560    if val.is_null() || out_int.is_null() {
561        return PolyXmlErrorCode::ErrNullPtr;
562    }
563    if let Some(i) = (*val).0.as_i64() {
564        *out_int = i;
565        PolyXmlErrorCode::Ok
566    } else {
567        PolyXmlErrorCode::ErrScalar
568    }
569}
570
571#[no_mangle]
572pub unsafe extern "C" fn polyxml_value_get_float(
573    val: *const PolyXmlValue,
574    out_float: *mut f64,
575) -> PolyXmlErrorCode {
576    if val.is_null() || out_float.is_null() {
577        return PolyXmlErrorCode::ErrNullPtr;
578    }
579    if let Some(f) = (*val).0.as_f64() {
580        *out_float = f;
581        PolyXmlErrorCode::Ok
582    } else {
583        PolyXmlErrorCode::ErrScalar
584    }
585}
586
587#[no_mangle]
588pub unsafe extern "C" fn polyxml_value_get_bool(
589    val: *const PolyXmlValue,
590    out_bool: *mut bool,
591) -> PolyXmlErrorCode {
592    if val.is_null() || out_bool.is_null() {
593        return PolyXmlErrorCode::ErrNullPtr;
594    }
595    if let Some(b) = (*val).0.as_bool() {
596        *out_bool = b;
597        PolyXmlErrorCode::Ok
598    } else {
599        PolyXmlErrorCode::ErrScalar
600    }
601}
602
603#[no_mangle]
604pub unsafe extern "C" fn polyxml_value_get_string(
605    val: *const PolyXmlValue,
606    out_str: *mut *const c_char,
607    out_len: *mut usize,
608) -> PolyXmlErrorCode {
609    if val.is_null() || out_str.is_null() || out_len.is_null() {
610        return PolyXmlErrorCode::ErrNullPtr;
611    }
612    if let Some(s) = (*val).0.as_str() {
613        *out_str = s.as_ptr() as *const c_char;
614        *out_len = s.len();
615        PolyXmlErrorCode::Ok
616    } else {
617        PolyXmlErrorCode::ErrScalar
618    }
619}
620
621#[no_mangle]
622pub unsafe extern "C" fn polyxml_value_get_list_len(
623    val: *const PolyXmlValue,
624    out_len: *mut usize,
625) -> PolyXmlErrorCode {
626    if val.is_null() || out_len.is_null() {
627        return PolyXmlErrorCode::ErrNullPtr;
628    }
629    if let Some(l) = (*val).0.as_list() {
630        *out_len = l.len();
631        PolyXmlErrorCode::Ok
632    } else {
633        PolyXmlErrorCode::ErrScalar
634    }
635}
636
637#[no_mangle]
638pub unsafe extern "C" fn polyxml_value_get_list_item(
639    val: *const PolyXmlValue,
640    idx: usize,
641) -> *const PolyXmlValue {
642    if val.is_null() {
643        return std::ptr::null();
644    }
645    if let Some(l) = (*val).0.as_list() {
646        if let Some(item) = l.get(idx) {
647            return item as *const PolyValue as *const PolyXmlValue;
648        }
649    }
650    std::ptr::null()
651}
652
653#[no_mangle]
654pub unsafe extern "C" fn polyxml_value_is_null(val: *const PolyXmlValue) -> bool {
655    if val.is_null() {
656        return true;
657    }
658    (*val).0.is_null()
659}
660
661#[no_mangle]
662pub unsafe extern "C" fn polyxml_value_free(val: *mut PolyXmlValue) {
663    if !val.is_null() {
664        drop(Box::from_raw(val));
665    }
666}
667
668#[no_mangle]
669pub extern "C" fn polyxml_version() -> *const c_char {
670    static VERSION: &[u8] = concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes();
671    VERSION.as_ptr() as *const c_char
672}
673
674#[cfg(test)]
675mod tests {
676    use super::*;
677    use std::ffi::CString;
678
679    #[test]
680    fn test_c_abi_namespaced_roundtrip() {
681        unsafe {
682            let name = CString::new("Order").unwrap();
683            let ns_order = CString::new("https://example.com/orders").unwrap();
684            let builder = polyxml_schema_builder_create(name.as_ptr());
685            polyxml_schema_builder_set_namespace(builder, ns_order.as_ptr());
686
687            let id_name = CString::new("id").unwrap();
688            let id_xml = CString::new("id").unwrap();
689            polyxml_schema_builder_add_field(
690                builder,
691                id_name.as_ptr(),
692                id_xml.as_ptr(),
693                PolyXmlFieldKind::Attribute,
694                PolyXmlScalarType::Int,
695            );
696
697            let item_name = CString::new("item").unwrap();
698            let item_xml = CString::new("item").unwrap();
699            let ns_item = CString::new("https://example.com/items").unwrap();
700            polyxml_schema_builder_add_field_with_namespace(
701                builder,
702                item_name.as_ptr(),
703                item_xml.as_ptr(),
704                PolyXmlFieldKind::Element,
705                PolyXmlScalarType::String,
706                ns_item.as_ptr(),
707            );
708
709            let schema = polyxml_schema_builder_build(builder);
710            assert!(!schema.is_null());
711
712            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>"#;
713            let mut val_ptr: *mut PolyXmlValue = std::ptr::null_mut();
714            let err = polyxml_deserialize(xml.as_ptr(), xml.len(), schema, &mut val_ptr);
715            assert_eq!(err as i32, PolyXmlErrorCode::Ok as i32);
716            assert!(!val_ptr.is_null());
717
718            let mut int_val = 0;
719            let id_field = polyxml_value_get_field(val_ptr, id_name.as_ptr());
720            assert!(!id_field.is_null());
721            polyxml_value_get_int(id_field, &mut int_val);
722            assert_eq!(int_val, 101);
723
724            let mut out_bytes: *mut u8 = std::ptr::null_mut();
725            let mut out_len = 0;
726            let p0 = CString::new("ord").unwrap();
727            let u0 = CString::new("https://example.com/orders").unwrap();
728            let prefixes = [p0.as_ptr()];
729            let uris = [u0.as_ptr()];
730
731            let serr = polyxml_serialize_with_options(
732                name.as_ptr(),
733                val_ptr,
734                schema,
735                0,
736                1,
737                prefixes.as_ptr(),
738                uris.as_ptr(),
739                1,
740                &mut out_bytes,
741                &mut out_len,
742            );
743            assert_eq!(serr as i32, PolyXmlErrorCode::Ok as i32);
744            assert!(!out_bytes.is_null());
745            let s = std::str::from_utf8(std::slice::from_raw_parts(out_bytes, out_len)).unwrap();
746            assert!(s.contains("xmlns:ord=\"https://example.com/orders\""));
747            assert!(s.contains("<ord:Order"));
748            assert!(s.contains("id=\"101\""));
749
750            polyxml_bytes_free(out_bytes, out_len);
751            polyxml_value_free(val_ptr);
752            polyxml_schema_free(schema);
753        }
754    }
755
756    #[test]
757    fn test_c_abi_nested_list_batch_roundtrip() {
758        unsafe {
759            // Build Sensor schema
760            let sensor_name = CString::new("Sensor").unwrap();
761            let s_builder = polyxml_schema_builder_create(sensor_name.as_ptr());
762            let id_str = CString::new("Id").unwrap();
763            let val_str = CString::new("Value").unwrap();
764            polyxml_schema_builder_add_field(
765                s_builder,
766                id_str.as_ptr(),
767                id_str.as_ptr(),
768                PolyXmlFieldKind::Element,
769                PolyXmlScalarType::String,
770            );
771            polyxml_schema_builder_add_field(
772                s_builder,
773                val_str.as_ptr(),
774                val_str.as_ptr(),
775                PolyXmlFieldKind::Element,
776                PolyXmlScalarType::Int,
777            );
778            let sensor_schema = polyxml_schema_builder_build(s_builder);
779            assert!(!sensor_schema.is_null());
780
781            // Build Batch schema with list of Sensor
782            let batch_name = CString::new("Batch").unwrap();
783            let b_builder = polyxml_schema_builder_create(batch_name.as_ptr());
784            let sensor_field_name = CString::new("Sensor").unwrap();
785            let sensor_xml_tag = CString::new("Sensor").unwrap();
786            polyxml_schema_builder_add_list_nested_field(
787                b_builder,
788                sensor_field_name.as_ptr(),
789                sensor_xml_tag.as_ptr(),
790                sensor_schema,
791                std::ptr::null(),
792            );
793            let batch_schema = polyxml_schema_builder_build(b_builder);
794            assert!(!batch_schema.is_null());
795
796            // Deserialize XML
797            let xml = br#"<Batch><Sensor><Id>sensor-0</Id><Value>10</Value></Sensor><Sensor><Id>sensor-1</Id><Value>20</Value></Sensor></Batch>"#;
798            let mut val_ptr: *mut PolyXmlValue = std::ptr::null_mut();
799            let err = polyxml_deserialize(xml.as_ptr(), xml.len(), batch_schema, &mut val_ptr);
800            assert_eq!(err as i32, PolyXmlErrorCode::Ok as i32);
801            assert!(!val_ptr.is_null());
802
803            // Inspect list length
804            let list_val = polyxml_value_get_field(val_ptr, sensor_field_name.as_ptr());
805            assert!(!list_val.is_null());
806            let mut len = 0;
807            let len_err = polyxml_value_get_list_len(list_val, &mut len);
808            assert_eq!(len_err as i32, PolyXmlErrorCode::Ok as i32);
809            assert_eq!(len, 2);
810
811            // Inspect item 0
812            let item0 = polyxml_value_get_list_item(list_val, 0);
813            assert!(!item0.is_null());
814            let id0 = polyxml_value_get_field(item0, id_str.as_ptr());
815            let mut id0_ptr: *const c_char = std::ptr::null();
816            let mut id0_len = 0;
817            polyxml_value_get_string(id0, &mut id0_ptr, &mut id0_len);
818            let id0_str =
819                std::str::from_utf8(std::slice::from_raw_parts(id0_ptr as *const u8, id0_len))
820                    .unwrap();
821            assert_eq!(id0_str, "sensor-0");
822            let v0 = polyxml_value_get_field(item0, val_str.as_ptr());
823            let mut int0 = 0;
824            polyxml_value_get_int(v0, &mut int0);
825            assert_eq!(int0, 10);
826
827            // Serialize back
828            let mut out_bytes: *mut u8 = std::ptr::null_mut();
829            let mut out_len = 0;
830            let serr = polyxml_serialize(
831                batch_name.as_ptr(),
832                val_ptr,
833                batch_schema,
834                0,
835                &mut out_bytes,
836                &mut out_len,
837            );
838            assert_eq!(serr as i32, PolyXmlErrorCode::Ok as i32);
839            let out_str =
840                std::str::from_utf8(std::slice::from_raw_parts(out_bytes, out_len)).unwrap();
841            assert_eq!(out_str, std::str::from_utf8(xml).unwrap());
842
843            polyxml_bytes_free(out_bytes, out_len);
844            polyxml_value_free(val_ptr);
845
846            // Construct Batch manually using C-ABI value constructors
847            let manual_batch = polyxml_value_create_record(batch_schema);
848            let manual_list = polyxml_value_create_list_with_capacity(2);
849            for i in 0..2 {
850                let s = polyxml_value_create_record(sensor_schema);
851                let id_val_str = format!("sensor-{}", i);
852                let c_id = polyxml_value_create_string(
853                    id_val_str.as_ptr() as *const c_char,
854                    id_val_str.len(),
855                );
856                polyxml_value_set_field(s, id_str.as_ptr(), c_id);
857                let c_val = polyxml_value_create_int((i + 1) * 10);
858                polyxml_value_set_field(s, val_str.as_ptr(), c_val);
859                polyxml_value_list_append(manual_list, s);
860            }
861            polyxml_value_set_field(manual_batch, sensor_field_name.as_ptr(), manual_list);
862
863            let mut manual_bytes: *mut u8 = std::ptr::null_mut();
864            let mut manual_len = 0;
865            let m_serr = polyxml_serialize(
866                batch_name.as_ptr(),
867                manual_batch,
868                batch_schema,
869                0,
870                &mut manual_bytes,
871                &mut manual_len,
872            );
873            assert_eq!(m_serr as i32, PolyXmlErrorCode::Ok as i32);
874            let manual_str =
875                std::str::from_utf8(std::slice::from_raw_parts(manual_bytes, manual_len)).unwrap();
876            assert_eq!(manual_str, std::str::from_utf8(xml).unwrap());
877
878            polyxml_bytes_free(manual_bytes, manual_len);
879            polyxml_value_free(manual_batch);
880            polyxml_schema_free(sensor_schema);
881            polyxml_schema_free(batch_schema);
882        }
883    }
884}