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).errFunc = handler
192 .map(|h| h as *mut std::ffi::c_void)
193 .unwrap_or(std::ptr::null_mut());
194 (*ctxt).errCtxt = ctx;
195 }
196}
197
198/// Report an XSLT error.
199///
200/// Logs an error message associated with the given transform context,
201/// stylesheet, and instruction node. The message is a printf-style format
202/// string followed by variadic arguments. The variadic arguments are not
203/// expanded (matching the safe subset); the raw message is recorded.
204///
205/// # Parameters
206///
207/// * `ctxt` — The transform context (may be null).
208/// * `style` — The stylesheet (may be null).
209/// * `inst` — The instruction node that triggered the error (may be null).
210/// * `msg` — The printf-style format string.
211pub fn xsltTransformError(
212 ctxt: *mut _xsltTransformContext,
213 style: *mut _xsltStylesheet,
214 inst: *mut _xmlNode,
215 msg: *const std::os::raw::c_char,
216) {
217 if msg.is_null() {
218 return;
219 }
220 // SAFETY: msg must be a valid NUL-terminated C string.
221 let bytes =
222 unsafe { core::slice::from_raw_parts(msg as *const u8, libc::strlen(msg) as usize) };
223 let text = String::from_utf8_lossy(bytes).into_owned();
224
225 // Record the last error.
226 if let Ok(mut last) = LAST_XSLT_ERROR.lock() {
227 *last = Some(text.clone().into_bytes());
228 }
229
230 // Build the prefix: "file:line: " when the instruction node provides it.
231 let mut prefix = String::new();
232 if !inst.is_null() {
233 // SAFETY: inst must be a valid node.
234 let node = unsafe { &*inst };
235 // SAFETY: node.doc must be valid while the node is alive.
236 let doc = unsafe { &*node.doc };
237 if !doc.URL.is_null() {
238 // SAFETY: URL must be a valid NUL-terminated string.
239 let url = unsafe {
240 core::slice::from_raw_parts(
241 doc.URL as *const u8,
242 libc::strlen(doc.URL as *const libc::c_char) as usize,
243 )
244 };
245 prefix.push_str(&String::from_utf8_lossy(url));
246 prefix.push(':');
247 prefix.push_str(&node.line.to_string());
248 prefix.push_str(": ");
249 }
250 }
251
252 // Invoke the per-context handler if one is registered.
253 if !ctxt.is_null() {
254 // SAFETY: ctxt must be a valid _xsltTransformContext.
255 let ctx = unsafe { &*ctxt };
256 if !ctx.errFunc.is_null() {
257 // SAFETY: errFunc is a valid handler registered by
258 // xsltSetTransformErrorFunc; errCtxt is the matching context.
259 let handler: unsafe extern "C" fn(*mut std::ffi::c_void, *const std::os::raw::c_char) =
260 unsafe { std::mem::transmute(ctx.errFunc) };
261 let full = format!("{}{}", prefix, text);
262 let mut cmsg = full.into_bytes();
263 cmsg.push(0);
264 unsafe { handler(ctx.errCtxt, cmsg.as_ptr() as *const std::os::raw::c_char) };
265 return;
266 }
267 }
268
269 // Default: write to stderr.
270 let full = format!("{}{}\n", prefix, text);
271 let _ = unsafe { libc::write(2, full.as_ptr() as *const libc::c_void, full.len()) };
272 let _ = style;
273}
274
275/// Get the last XSLT error message as a NUL-terminated heap string.
276///
277/// Returns a pointer to the last error message, or `std::ptr::null_mut()`
278/// if no error has occurred. The caller frees with `libc::free`.
279pub fn xsltGetLastError() -> *mut std::ffi::c_void {
280 let guard = match LAST_XSLT_ERROR.lock() {
281 Ok(g) => g,
282 Err(_) => return std::ptr::null_mut(),
283 };
284 match guard.as_ref() {
285 Some(bytes) => {
286 let len = bytes.len();
287 // SAFETY: malloc returns writable memory or NULL.
288 let p = unsafe { libc::malloc(len + 1) } as *mut u8;
289 if p.is_null() {
290 return std::ptr::null_mut();
291 }
292 unsafe {
293 core::ptr::copy_nonoverlapping(bytes.as_ptr(), p, len);
294 *p.add(len) = 0;
295 }
296 p as *mut std::ffi::c_void
297 }
298 None => std::ptr::null_mut(),
299 }
300}