Skip to main content

libxml2_rs/
lib.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4
5include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
6
7#[cfg(test)]
8mod tests {
9    use crate::{
10        xmlCleanupParser, xmlError, xmlInitParser, xmlSchemaCleanupTypes, xmlSchemaFree,
11        xmlSchemaFreeParserCtxt, xmlSchemaNewParserCtxt, xmlSchemaParse, xmlSchemaParserCtxtPtr,
12        xmlSchemaPtr, xmlSchemaSetParserStructuredErrors,
13    };
14    use ctor::{ctor, dtor};
15    use serial_test::serial;
16    use std::ffi::{c_void, CStr, CString};
17    use std::path::Path;
18    use workspace_root::get_workspace_root;
19
20    struct ValidationErrors {
21        errors: Vec<String>,
22    }
23
24    unsafe extern "C" fn structured_error_handler(
25        user_data: *mut c_void,
26        #[cfg(not(target_os = "windows"))] error: *mut xmlError,
27        #[cfg(target_os = "windows")] error: *const xmlError,
28    ) {
29        if error.is_null() {
30            return;
31        }
32
33        let line = unsafe { (*error).line };
34        let column = unsafe { (*error).int2 };
35        let message = unsafe { CStr::from_ptr((*error).message).to_str().unwrap() };
36
37        let context = unsafe { &mut *(user_data as *mut ValidationErrors) };
38
39        let error = format!("Error: Line {}, column {}: {}", line, column, message);
40
41        context.errors.push(error);
42    }
43
44    struct SchemaParserContext(xmlSchemaParserCtxtPtr);
45
46    impl SchemaParserContext {
47        fn new(path: &Path) -> Result<Self, Vec<String>> {
48            let path_str = match path.to_str() {
49                Some(str) => str,
50                None => return Err(vec![format!("Invalid path: {}", path.to_str().unwrap())]),
51            };
52
53            let c_path = match CString::new(path_str) {
54                Ok(str) => str,
55                Err(e) => return Err(vec![format!("Error: {}", e.to_string())]),
56            };
57
58            let context_ptr = unsafe { xmlSchemaNewParserCtxt(c_path.as_ptr()) };
59
60            if context_ptr.is_null() {
61                let err: Vec<String> =
62                    Vec::from(["Failed to create schema parser context".to_string()]);
63
64                Err(err)
65            } else {
66                Ok(Self(context_ptr))
67            }
68        }
69    }
70
71    impl Drop for SchemaParserContext {
72        fn drop(&mut self) {
73            unsafe { xmlSchemaFreeParserCtxt(self.0) };
74        }
75    }
76
77    struct Schema(xmlSchemaPtr);
78
79    impl Drop for Schema {
80        fn drop(&mut self) {
81            unsafe { xmlSchemaFree(self.0) };
82        }
83    }
84
85    #[ctor]
86    fn init() {
87        println!("Setting up xml schema environment");
88        unsafe { xmlInitParser() };
89    }
90
91    #[dtor]
92    fn clean_up() {
93        println!("Cleaning up xml schema");
94        unsafe {
95            xmlSchemaCleanupTypes();
96            xmlCleanupParser();
97        }
98    }
99
100    fn validate_xsd_schema(schema_file: &Path) -> Result<(), Vec<String>> {
101        let mut error_context = ValidationErrors { errors: Vec::new() };
102
103        let result: Result<(), Vec<String>> = (|| {
104            // Create the parser context using our safe wrapper.
105            let parser_context = SchemaParserContext::new(schema_file)?;
106
107            // Set the structured error handler. This is the modern, safe way.
108            unsafe {
109                xmlSchemaSetParserStructuredErrors(
110                    parser_context.0,
111                    Some(structured_error_handler),
112                    &mut error_context as *mut _ as *mut c_void,
113                );
114            }
115
116            // Parse the schema. The result will be managed by our `Schema` wrapper.
117            let schema_ptr = unsafe { xmlSchemaParse(parser_context.0) };
118            if schema_ptr.is_null() {
119                // If parsing fails, the error handler should have been called.
120                // We return an empty error if none were captured for some reason.
121                return Err(vec![
122                    "Schema parsing failed: file is not a valid XSD schema.".to_string(),
123                ]);
124            }
125
126            // Wrap the raw pointer in a guard to ensure it's freed.
127            let _schema = Schema(schema_ptr);
128
129            // If the schema was parsed but we collected errors, it's still a failure.
130            if !error_context.errors.is_empty() {
131                return Err(Vec::new()); // Errors are already in error_context
132            }
133
134            Ok(())
135        })();
136
137        match result {
138            Ok(()) => Ok(()),
139            Err(mut e) => {
140                e.extend(error_context.errors);
141
142                Err(e)
143            }
144        }
145    }
146
147    #[serial]
148    #[test]
149    fn test_xsd() {
150        let root = get_workspace_root();
151        let path = root.join("examples").join("valid.xsd");
152        println!("Path: {}", path.display());
153
154        let result = validate_xsd_schema(path.as_path());
155        assert!(result.is_ok());
156    }
157
158    #[serial]
159    #[test]
160    fn test_invalid_xsd() {
161        let root = get_workspace_root();
162        let path = root.join("examples").join("invalid.xsd");
163        println!("Path: {}", path.display());
164
165        let result = validate_xsd_schema(path.as_path());
166        assert!(result.is_err());
167    }
168}