libxml_rs/xslt/errors/mod.rs
1//! XSLT error handling (§33, §85 Phase 8).
2//!
3//! Defines error domains, error levels, error handler types, and the
4//! public API for reporting and retrieving XSLT errors.
5//!
6//! # Phase 8 status
7//!
8//! Constants and function types are fully defined. Functions are stubbed
9//! and will be implemented as part of Phase 8.
10
11use crate::abi::structs::*;
12use std::os::raw::c_int;
13
14// ── Error domains ─────────────────────────────────────────────────────────
15//
16// These constants identify the category of an XSLT error.
17// Source: xslt.h / xsltInternals.h (libxslt 1.1.39).
18
19/// No error.
20pub const XSLT_ERR_NONE: c_int = 0;
21
22/// Unknown error.
23pub const XSLT_ERR_UNKNOWN: c_int = 1;
24
25/// Missing required namespace.
26pub const XSLT_ERR_MISSING_NAMESPACE: c_int = 2;
27
28/// Invalid namespace.
29pub const XSLT_ERR_INVALID_NAMESPACE: c_int = 3;
30
31/// Missing required attribute.
32pub const XSLT_ERR_MISSING_ATTRIBUTE: c_int = 4;
33
34/// Invalid attribute value.
35pub const XSLT_ERR_INVALID_ATTRIBUTE: c_int = 5;
36
37/// Missing required element.
38pub const XSLT_ERR_MISSING_ELEMENT: c_int = 6;
39
40/// Invalid element.
41pub const XSLT_ERR_INVALID_ELEMENT: c_int = 7;
42
43/// Missing match attribute.
44pub const XSLT_ERR_MISSING_MATCH: c_int = 8;
45
46/// Missing name attribute.
47pub const XSLT_ERR_MISSING_NAME: c_int = 9;
48
49/// Missing select attribute.
50pub const XSLT_ERR_MISSING_SELECT: c_int = 10;
51
52/// Missing test attribute.
53pub const XSLT_ERR_MISSING_TEST: c_int = 11;
54
55/// Missing use attribute.
56pub const XSLT_ERR_MISSING_USE: c_int = 12;
57
58/// Invalid match pattern.
59pub const XSLT_ERR_INVALID_MATCH: c_int = 13;
60
61/// Invalid select expression.
62pub const XSLT_ERR_INVALID_SELECT: c_int = 14;
63
64/// Invalid test expression.
65pub const XSLT_ERR_INVALID_TEST: c_int = 15;
66
67/// Invalid use expression.
68pub const XSLT_ERR_INVALID_USE: c_int = 16;
69
70/// Missing namespace.
71pub const XSLT_ERR_MISSING_NS: c_int = 17;
72
73/// Cyclic reference detected.
74pub const XSLT_ERR_CYCLIC_REFERENCE: c_int = 18;
75
76/// Recursion limit exceeded.
77pub const XSLT_ERR_RECURSION: c_int = 19;
78
79/// Internal XSLT error.
80pub const XSLT_ERR_INTERNAL: c_int = 20;
81
82// ── Error levels ──────────────────────────────────────────────────────────
83//
84// These constants indicate the severity of an XSLT error.
85// Source: xslt.h (libxslt 1.1.39).
86
87/// No error level (unset).
88pub const XSLT_ERR_LEVEL_NONE: c_int = 0;
89
90/// Warning — non-fatal issue.
91pub const XSLT_ERR_LEVEL_WARNING: c_int = 1;
92
93/// Error — processing may continue but results may be incomplete.
94pub const XSLT_ERR_LEVEL_ERROR: c_int = 2;
95
96/// Fatal error — processing cannot continue.
97pub const XSLT_ERR_LEVEL_FATAL: c_int = 3;
98
99// ── Error handler types ───────────────────────────────────────────────────
100
101/// Global XSLT error handler function type.
102///
103/// Matches the upstream `xsltTransformErrorFunc` typedef:
104/// ```c
105/// typedef void (*xsltTransformErrorFunc)(void *ctxt, void *ctx,
106/// xsltStylesheetPtr style,
107/// const xmlChar *msg, ...);
108/// ```
109pub type xsltTransformErrorFunc = Option<
110 unsafe extern "C" fn(
111 *mut std::ffi::c_void,
112 *mut std::ffi::c_void,
113 *mut _xsltStylesheet,
114 *const crate::abi::types::xmlChar,
115 ...
116 ),
117>;
118
119// ── Public API ────────────────────────────────────────────────────────────
120
121/// The last XSLT error message (thread-local).
122use std::sync::Mutex;
123
124static LAST_XSLT_ERROR: Mutex<Option<Vec<u8>>> = Mutex::new(None);
125
126/// Global debug handler (upstream xsltGenericDebug).
127static mut XSLT_GENERIC_DEBUG: Option<
128 unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char),
129> = None;
130
131/// Set the generic debug handler (upstream `xsltSetGenericDebugFunc`).
132///
133/// # UPSTREAM-PARITY
134///
135/// ```c
136/// void xsltSetGenericDebugFunc(void *ctx, xmlGenericErrorFunc handler);
137/// ```
138///
139/// With a NULL handler, messages go to `stderr`; with a NULL context they
140/// are suppressed (upstream's default debug handler checks the context).
141#[no_mangle]
142pub unsafe extern "C" fn xsltSetGenericDebugFunc(
143 ctx: *mut std::ffi::c_void,
144 handler: Option<unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char)>,
145) {
146 unsafe {
147 XSLT_GENERIC_DEBUG_CONTEXT = ctx;
148 if handler.is_some() {
149 XSLT_GENERIC_DEBUG = handler;
150 }
151 }
152}
153
154static mut XSLT_GENERIC_DEBUG_CONTEXT: *mut std::ffi::c_void = std::ptr::null_mut();
155
156/// Emit a generic debug message (upstream xsltGenericDebug).
157#[no_mangle]
158pub unsafe extern "C" fn xsltGenericDebug(
159 ctx: *mut std::ffi::c_void,
160 msg: *const std::os::raw::c_char,
161) {
162 if ctx.is_null() || msg.is_null() {
163 return;
164 }
165 let len = libc::strlen(msg);
166 libc::write(2, msg as *const libc::c_void, len);
167}
168
169/// Set the transform error handler for a context.
170///
171/// Registers a per-context error handler that will be called for every
172/// error reported during the transformation. Pass `None` to restore the
173/// default handler.
174///
175/// # Parameters
176///
177/// * `ctxt` — The transform context, or `std::ptr::null_mut()` for the
178/// global handler.
179/// * `ctx` — Opaque user-data pointer passed to the handler.
180/// * `handler` — The error handler function, or `None` to reset.
181pub fn xsltSetTransformErrorFunc(
182 ctxt: *mut _xsltTransformContext,
183 ctx: *mut std::ffi::c_void,
184 handler: Option<unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char)>,
185) {
186 if ctxt.is_null() {
187 return;
188 }
189 // SAFETY: ctxt must be a valid _xsltTransformContext.
190 unsafe {
191 (*ctxt).error = handler;
192 (*ctxt).errctx = ctx;
193 }
194}
195
196/// Report an XSLT error.
197///
198/// Logs an error message associated with the given transform context,
199/// stylesheet, and instruction node. The message is a printf-style format
200/// string followed by variadic arguments. The variadic arguments are not
201/// expanded (matching the safe subset); the raw message is recorded.
202///
203/// # Parameters
204///
205/// * `ctxt` — The transform context (may be null).
206/// * `style` — The stylesheet (may be null).
207/// * `inst` — The instruction node that triggered the error (may be null).
208/// * `msg` — The printf-style format string.
209pub fn xsltTransformError(
210 ctxt: *mut _xsltTransformContext,
211 style: *mut _xsltStylesheet,
212 inst: *mut _xmlNode,
213 msg: *const std::os::raw::c_char,
214) {
215 if msg.is_null() {
216 return;
217 }
218 // SAFETY: msg must be a valid NUL-terminated C string.
219 let bytes =
220 unsafe { core::slice::from_raw_parts(msg as *const u8, libc::strlen(msg) as usize) };
221 let text = String::from_utf8_lossy(bytes).into_owned();
222
223 // Record the last error.
224 if let Ok(mut last) = LAST_XSLT_ERROR.lock() {
225 *last = Some(text.clone().into_bytes());
226 }
227
228 // Build the prefix: "file:line: " when the instruction node provides it.
229 let mut prefix = String::new();
230 if !inst.is_null() {
231 // SAFETY: inst must be a valid node.
232 let node = unsafe { &*inst };
233 // SAFETY: node.doc must be valid while the node is alive.
234 let doc = unsafe { &*node.doc };
235 if !doc.URL.is_null() {
236 // SAFETY: URL must be a valid NUL-terminated string.
237 let url = unsafe {
238 core::slice::from_raw_parts(
239 doc.URL as *const u8,
240 libc::strlen(doc.URL as *const libc::c_char) as usize,
241 )
242 };
243 prefix.push_str(&String::from_utf8_lossy(url));
244 prefix.push(':');
245 prefix.push_str(&node.line.to_string());
246 prefix.push_str(": ");
247 }
248 }
249
250 // Invoke the per-context handler if one is registered.
251 if !ctxt.is_null() {
252 // SAFETY: ctxt must be a valid _xsltTransformContext.
253 let ctx = unsafe { &*ctxt };
254 if let Some(handler) = ctx.error {
255 let full = format!("{}{}", prefix, text);
256 let mut cmsg = full.into_bytes();
257 cmsg.push(0);
258 unsafe { handler(ctx.errctx, cmsg.as_ptr() as *const std::os::raw::c_char) };
259 return;
260 }
261 }
262
263 // Default: write to stderr.
264 let full = format!("{}{}\n", prefix, text);
265 let _ = unsafe { libc::write(2, full.as_ptr() as *const libc::c_void, full.len()) };
266 let _ = style;
267}
268
269/// Get the last XSLT error message as a NUL-terminated heap string.
270///
271/// Returns a pointer to the last error message, or `std::ptr::null_mut()`
272/// if no error has occurred. The caller frees with `libc::free`.
273pub fn xsltGetLastError() -> *mut std::ffi::c_void {
274 let guard = match LAST_XSLT_ERROR.lock() {
275 Ok(g) => g,
276 Err(_) => return std::ptr::null_mut(),
277 };
278 match guard.as_ref() {
279 Some(bytes) => {
280 let len = bytes.len();
281 // SAFETY: malloc returns writable memory or NULL.
282 let p = unsafe { libc::malloc(len + 1) } as *mut u8;
283 if p.is_null() {
284 return std::ptr::null_mut();
285 }
286 unsafe {
287 core::ptr::copy_nonoverlapping(bytes.as_ptr(), p, len);
288 *p.add(len) = 0;
289 }
290 p as *mut std::ffi::c_void
291 }
292 None => std::ptr::null_mut(),
293 }
294}