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;
13use std::ptr;
14
15// ── Error domains ─────────────────────────────────────────────────────────
16//
17// These constants identify the category of an XSLT error.
18// Source: xslt.h / xsltInternals.h (libxslt 1.1.45).
19
20/// No error.
21pub const XSLT_ERR_NONE: c_int = 0;
22
23/// Unknown error.
24pub const XSLT_ERR_UNKNOWN: c_int = 1;
25
26/// Missing required namespace.
27pub const XSLT_ERR_MISSING_NAMESPACE: c_int = 2;
28
29/// Invalid namespace.
30pub const XSLT_ERR_INVALID_NAMESPACE: c_int = 3;
31
32/// Missing required attribute.
33pub const XSLT_ERR_MISSING_ATTRIBUTE: c_int = 4;
34
35/// Invalid attribute value.
36pub const XSLT_ERR_INVALID_ATTRIBUTE: c_int = 5;
37
38/// Missing required element.
39pub const XSLT_ERR_MISSING_ELEMENT: c_int = 6;
40
41/// Invalid element.
42pub const XSLT_ERR_INVALID_ELEMENT: c_int = 7;
43
44/// Missing match attribute.
45pub const XSLT_ERR_MISSING_MATCH: c_int = 8;
46
47/// Missing name attribute.
48pub const XSLT_ERR_MISSING_NAME: c_int = 9;
49
50/// Missing select attribute.
51pub const XSLT_ERR_MISSING_SELECT: c_int = 10;
52
53/// Missing test attribute.
54pub const XSLT_ERR_MISSING_TEST: c_int = 11;
55
56/// Missing use attribute.
57pub const XSLT_ERR_MISSING_USE: c_int = 12;
58
59/// Invalid match pattern.
60pub const XSLT_ERR_INVALID_MATCH: c_int = 13;
61
62/// Invalid select expression.
63pub const XSLT_ERR_INVALID_SELECT: c_int = 14;
64
65/// Invalid test expression.
66pub const XSLT_ERR_INVALID_TEST: c_int = 15;
67
68/// Invalid use expression.
69pub const XSLT_ERR_INVALID_USE: c_int = 16;
70
71/// Missing namespace.
72pub const XSLT_ERR_MISSING_NS: c_int = 17;
73
74/// Cyclic reference detected.
75pub const XSLT_ERR_CYCLIC_REFERENCE: c_int = 18;
76
77/// Recursion limit exceeded.
78pub const XSLT_ERR_RECURSION: c_int = 19;
79
80/// Internal XSLT error.
81pub const XSLT_ERR_INTERNAL: c_int = 20;
82
83// ── Error levels ──────────────────────────────────────────────────────────
84//
85// These constants indicate the severity of an XSLT error.
86// Source: xslt.h (libxslt 1.1.45).
87
88/// No error level (unset).
89pub const XSLT_ERR_LEVEL_NONE: c_int = 0;
90
91/// Warning — non-fatal issue.
92pub const XSLT_ERR_LEVEL_WARNING: c_int = 1;
93
94/// Error — processing may continue but results may be incomplete.
95pub const XSLT_ERR_LEVEL_ERROR: c_int = 2;
96
97/// Fatal error — processing cannot continue.
98pub const XSLT_ERR_LEVEL_FATAL: c_int = 3;
99
100// ── Error handler types ───────────────────────────────────────────────────
101
102/// Global XSLT error handler function type.
103///
104/// Matches the upstream `xsltTransformErrorFunc` typedef:
105/// ```c
106/// typedef void (*xsltTransformErrorFunc)(void *ctxt, void *ctx,
107/// xsltStylesheetPtr style,
108/// const xmlChar *msg, ...);
109/// ```
110pub type xsltTransformErrorFunc = Option<
111 unsafe extern "C" fn(
112 *mut std::ffi::c_void,
113 *mut std::ffi::c_void,
114 *mut _xsltStylesheet,
115 *const crate::abi::types::xmlChar,
116 ...
117 ),
118>;
119
120// ── Public API ────────────────────────────────────────────────────────────
121
122/// The last XSLT error message (thread-local).
123use std::sync::Mutex;
124
125static LAST_XSLT_ERROR: Mutex<Option<Vec<u8>>> = Mutex::new(None);
126
127/// Global debug handler (upstream xsltGenericDebug).
128static mut XSLT_GENERIC_DEBUG: Option<
129 unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char),
130> = None;
131
132/// Set the generic debug handler (upstream `xsltSetGenericDebugFunc`).
133///
134/// # UPSTREAM-PARITY
135///
136/// ```c
137/// void xsltSetGenericDebugFunc(void *ctx, xmlGenericErrorFunc handler);
138/// ```
139///
140/// With a NULL handler, messages go to `stderr`; with a NULL context they
141/// are suppressed (upstream's default debug handler checks the context).
142///
143/// # SAFETY
144///
145/// - `ctx` must be valid pointers (or NULL
146/// where the upstream C contract allows), obtained from the
147/// matching constructor/owner and not yet freed; the callee may
148/// take or keep ownership exactly as the C API specifies.
149///
150/// - `handler` must be a valid callback (or None);
151/// the callback is invoked with the documented context pointer and
152/// must itself uphold the same pointer invariants.
153///
154/// The caller must not race this call with concurrent mutation of the
155/// same objects from other threads (per-object state is not internally
156/// synchronized). Violating any of the above is undefined behavior.
157///
158/// Exercised by the C-API differential courts
159/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
160/// courts; those pass byte-for-byte against the upstream oracle.
161#[no_mangle]
162pub unsafe extern "C" fn xsltSetGenericDebugFunc(
163 ctx: *mut std::ffi::c_void,
164 handler: Option<unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char)>,
165) {
166 unsafe {
167 XSLT_GENERIC_DEBUG_CONTEXT = ctx;
168 if handler.is_some() {
169 XSLT_GENERIC_DEBUG = handler;
170 }
171 }
172}
173
174static mut XSLT_GENERIC_DEBUG_CONTEXT: *mut std::ffi::c_void = std::ptr::null_mut();
175
176/// Emit a generic debug message (upstream xsltGenericDebug).
177///
178/// # SAFETY
179///
180/// - `ctx`, `msg` must be valid pointers (or NULL
181/// where the upstream C contract allows), obtained from the
182/// matching constructor/owner and not yet freed; the callee may
183/// take or keep ownership exactly as the C API specifies.
184///
185/// The caller must not race this call with concurrent mutation of the
186/// same objects from other threads (per-object state is not internally
187/// synchronized). Violating any of the above is undefined behavior.
188///
189/// Exercised by the C-API differential courts
190/// (courts/suites/data-abi/*-family-probe.c) and the CLI differential
191/// courts; those pass byte-for-byte against the upstream oracle.
192#[no_mangle]
193pub unsafe extern "C" fn xsltGenericDebug(
194 ctx: *mut std::ffi::c_void,
195 msg: *const std::os::raw::c_char,
196) {
197 if ctx.is_null() || msg.is_null() {
198 return;
199 }
200 let len = libc::strlen(msg);
201 libc::write(2, msg as *const libc::c_void, len);
202}
203
204/// Set the transform error handler for a context.
205///
206/// Registers a per-context error handler that will be called for every
207/// error reported during the transformation. Pass `None` to restore the
208/// default handler.
209///
210/// # Parameters
211///
212/// * `ctxt` — The transform context, or `std::ptr::null_mut()` for the
213/// global handler.
214/// * `ctx` — Opaque user-data pointer passed to the handler.
215/// * `handler` — The error handler function, or `None` to reset.
216pub fn xsltSetTransformErrorFunc(
217 ctxt: *mut _xsltTransformContext,
218 ctx: *mut std::ffi::c_void,
219 handler: Option<unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char)>,
220) {
221 if ctxt.is_null() {
222 return;
223 }
224 // SAFETY: ctxt must be a valid _xsltTransformContext.
225 unsafe {
226 (*ctxt).error = handler;
227 (*ctxt).errctx = ctx;
228 }
229}
230
231/// Report an XSLT error.
232///
233/// Faithful port of upstream xsltutils.c `xsltTransformError`: the
234/// transform context is moved to the error state, the error context line
235/// is printed (upstream `xsltPrintErrorContext`), and the message is
236/// emitted verbatim through the registered handler or stderr. Messages
237/// carry their own trailing newline, exactly as upstream's do — no
238/// newline is added here.
239///
240/// The upstream signature is variadic (`const char *msg, ...`); the
241/// candidate's callers format the message before calling (a `%s`/`%d`
242/// placeholder is never expanded by this function).
243///
244/// # Parameters
245///
246/// * `ctxt` — The transform context (may be null).
247/// * `style` — The stylesheet (may be null).
248/// * `inst` — The instruction node that triggered the error (may be null).
249/// * `msg` — The message, NUL-terminated, typically ending in `\n`.
250pub fn xsltTransformError(
251 ctxt: *mut _xsltTransformContext,
252 style: *mut _xsltStylesheet,
253 inst: *mut _xmlNode,
254 msg: *const std::os::raw::c_char,
255) {
256 if msg.is_null() {
257 return;
258 }
259 // SAFETY: msg must be a valid NUL-terminated C string.
260 let bytes =
261 unsafe { core::slice::from_raw_parts(msg as *const u8, libc::strlen(msg) as usize) };
262 let text = String::from_utf8_lossy(bytes).into_owned();
263
264 // Record the last error (the raw message, as upstream stores the
265 // formatted message).
266 if let Ok(mut last) = LAST_XSLT_ERROR.lock() {
267 *last = Some(text.clone().into_bytes());
268 }
269
270 // UPSTREAM-PARITY (xsltutils.c xsltTransformError): an error moves the
271 // transform context out of the OK state.
272 if !ctxt.is_null() {
273 // SAFETY: ctxt must be a valid _xsltTransformContext.
274 let ctx = unsafe { &mut *ctxt };
275 if ctx.state == crate::xslt::transform::XSLT_STATE_OK {
276 ctx.state = crate::xslt::transform::XSLT_STATE_ERROR;
277 }
278 let mut node = inst;
279 if node.is_null() {
280 node = ctx.inst;
281 }
282 // Build the context line (xsltPrintErrorContext) and the full
283 // message, then emit through the handler if one is registered.
284 let context_line = print_error_context(ctxt, style, node);
285 let full = format!("{}{}", context_line, text);
286 let mut cmsg = full.into_bytes();
287 let msg_len = cmsg.len();
288 cmsg.push(0);
289 let ctx = unsafe { &*ctxt };
290 if let Some(handler) = ctx.error {
291 unsafe { handler(ctx.errctx, cmsg.as_ptr() as *const std::os::raw::c_char) };
292 return;
293 }
294 let _ = unsafe { libc::write(2, cmsg.as_ptr() as *const libc::c_void, msg_len) };
295 return;
296 }
297
298 // No transform context: compile-time errors and standalone messages.
299 // (Upstream xsltPrintErrorContext is still invoked with NULL ctxt and
300 // the given style/node.)
301 let context_line = print_error_context(ptr::null_mut(), style, inst);
302 let full = format!("{}{}", context_line, text);
303 let mut cmsg = full.into_bytes();
304 let msg_len = cmsg.len();
305 cmsg.push(0);
306 let _ = unsafe { libc::write(2, cmsg.as_ptr() as *const libc::c_void, msg_len) };
307 let _ = style;
308}
309
310/// Build the error context line printed before an XSLT error message
311/// (upstream xsltutils.c `xsltPrintErrorContext`). The line is one of:
312///
313/// ```text
314/// error\n
315/// error: file F\n
316/// error: file F line N\n
317/// error: file F element E\n
318/// error: file F line N element E\n
319/// error: element E\n
320/// compilation error ... / runtime error ...
321/// ```
322fn print_error_context(
323 ctxt: *mut _xsltTransformContext,
324 style: *mut _xsltStylesheet,
325 node: *mut _xmlNode,
326) -> String {
327 let mut line = 0i64;
328 let mut file: *const std::os::raw::c_char = ptr::null();
329 let mut name: *const std::os::raw::c_char = ptr::null();
330
331 if !node.is_null() {
332 // SAFETY: node must be valid.
333 let node_ref = unsafe { &*node };
334 if node_ref.type_ == crate::abi::types::xmlElementType::XML_DOCUMENT_NODE as c_int
335 || node_ref.type_ == crate::abi::types::xmlElementType::XML_HTML_DOCUMENT_NODE as c_int
336 {
337 let doc = node as *mut crate::abi::structs::_xmlDoc;
338 // SAFETY: doc->URL is a valid NUL-terminated string or NULL.
339 file = unsafe { (*doc).URL } as *const std::os::raw::c_char;
340 } else {
341 line = crate::abi::exports_xml2::xmlGetLineNo(node) as i64;
342 // SAFETY: node->doc must be valid while the node is alive.
343 let doc = { node_ref.doc };
344 if !doc.is_null() {
345 file = unsafe { (*doc).URL } as *const std::os::raw::c_char;
346 }
347 name = node_ref.name as *const std::os::raw::c_char;
348 }
349 }
350
351 let errtype = if !ctxt.is_null() {
352 "runtime error"
353 } else if !style.is_null() {
354 "compilation error"
355 } else {
356 "error"
357 };
358
359 let s = |p: *const std::os::raw::c_char| -> String {
360 if p.is_null() {
361 String::new()
362 } else {
363 unsafe { std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned() }
364 }
365 };
366 let file_s = s(file);
367 let name_s = s(name);
368 let has_file = !file.is_null();
369 let has_name = !name.is_null();
370
371 if has_file && line != 0 && has_name {
372 format!(
373 "{}: file {} line {} element {}\n",
374 errtype, file_s, line, name_s
375 )
376 } else if has_file && has_name {
377 format!("{}: file {} element {}\n", errtype, file_s, name_s)
378 } else if has_file && line != 0 {
379 format!("{}: file {} line {}\n", errtype, file_s, line)
380 } else if has_file {
381 format!("{}: file {}\n", errtype, file_s)
382 } else if has_name {
383 format!("{}: element {}\n", errtype, name_s)
384 } else {
385 format!("{}\n", errtype)
386 }
387}
388
389/// Get the last XSLT error message as a NUL-terminated heap string.
390///
391/// Returns a pointer to the last error message, or `std::ptr::null_mut()`
392/// if no error has occurred. The caller frees with `libc::free`.
393pub fn xsltGetLastError() -> *mut std::ffi::c_void {
394 let guard = match LAST_XSLT_ERROR.lock() {
395 Ok(g) => g,
396 Err(_) => return std::ptr::null_mut(),
397 };
398 match guard.as_ref() {
399 Some(bytes) => {
400 let len = bytes.len();
401 // SAFETY: malloc returns writable memory or NULL.
402 let p = unsafe { libc::malloc(len + 1) } as *mut u8;
403 if p.is_null() {
404 return std::ptr::null_mut();
405 }
406 unsafe {
407 core::ptr::copy_nonoverlapping(bytes.as_ptr(), p, len);
408 *p.add(len) = 0;
409 }
410 p as *mut std::ffi::c_void
411 }
412 None => std::ptr::null_mut(),
413 }
414}