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
12pub 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 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}