Skip to main content

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}