Skip to main content

readstat/
rs_parser.rs

1//! Safe wrapper around the `ReadStat` C parser.
2//!
3//! [`ReadStatParser`] provides a builder-pattern API for configuring and invoking the
4//! `ReadStat` C library's `readstat_parser_t`. It manages the parser lifecycle (init/free)
5//! via RAII and exposes methods for setting callback handlers, row limits/offsets,
6//! and triggering the actual `.sas7bdat` parse.
7
8#![allow(
9    clippy::cast_possible_wrap,
10    clippy::cast_possible_truncation,
11    clippy::cast_lossless
12)]
13
14use log::debug;
15use std::os::raw::{c_char, c_long, c_void};
16
17use crate::err::{ReadStatCError, ReadStatError, check_c_error};
18
19/// Safe RAII wrapper around the `ReadStat` C parser (`readstat_parser_t`).
20///
21/// Provides a builder-pattern API for configuring callbacks, row limits/offsets,
22/// and invoking the parse. The underlying C parser is freed on drop.
23pub(crate) struct ReadStatParser {
24    parser: *mut readstat_sys::readstat_parser_t,
25}
26
27impl ReadStatParser {
28    /// Allocates and initializes a new `ReadStat` C parser.
29    ///
30    /// # Errors
31    ///
32    /// Returns [`ReadStatError::CLibrary`] with
33    /// [`READSTAT_ERROR_MALLOC`](ReadStatCError::READSTAT_ERROR_MALLOC) if the C
34    /// library fails to allocate the parser (`readstat_parser_init` returns NULL).
35    /// Calling a handler-set method on a NULL parser would dereference it inside C
36    /// and segfault, so the failure is surfaced here instead.
37    pub(crate) fn new() -> Result<Self, ReadStatError> {
38        let parser: *mut readstat_sys::readstat_parser_t =
39            unsafe { readstat_sys::readstat_parser_init() };
40
41        if parser.is_null() {
42            return Err(ReadStatError::CLibrary(
43                ReadStatCError::READSTAT_ERROR_MALLOC,
44            ));
45        }
46
47        Ok(Self { parser })
48    }
49
50    /// Registers the callback invoked when file-level metadata is parsed.
51    pub(crate) fn set_metadata_handler(
52        self,
53        metadata_handler: readstat_sys::readstat_metadata_handler,
54    ) -> Result<Self, ReadStatError> {
55        let set_metadata_handler_error =
56            unsafe { readstat_sys::readstat_set_metadata_handler(self.parser, metadata_handler) };
57
58        debug!("After setting metadata handler, error ==> {set_metadata_handler_error}");
59
60        check_c_error(set_metadata_handler_error as i32)?;
61        Ok(self)
62    }
63
64    /// Sets the maximum number of rows to read. `None` means no limit.
65    pub(crate) fn set_row_limit(self, row_limit: Option<u32>) -> Result<Self, ReadStatError> {
66        if let Some(r) = row_limit {
67            let set_row_limit_error =
68                unsafe { readstat_sys::readstat_set_row_limit(self.parser, r as c_long) };
69
70            debug!("After setting row limit, error ==> {set_row_limit_error}");
71
72            check_c_error(set_row_limit_error as i32)?;
73        }
74        Ok(self)
75    }
76
77    /// Sets the starting row offset for reading. `None` means start from row 0.
78    pub(crate) fn set_row_offset(self, row_offset: Option<u32>) -> Result<Self, ReadStatError> {
79        if let Some(r) = row_offset {
80            let set_row_offset_error =
81                unsafe { readstat_sys::readstat_set_row_offset(self.parser, r as c_long) };
82
83            debug!("After setting row offset, error ==> {set_row_offset_error}");
84
85            check_c_error(set_row_offset_error as i32)?;
86        }
87        Ok(self)
88    }
89
90    /// Registers the callback invoked for each variable (column) definition.
91    pub(crate) fn set_variable_handler(
92        self,
93        variable_handler: readstat_sys::readstat_variable_handler,
94    ) -> Result<Self, ReadStatError> {
95        let set_variable_handler_error =
96            unsafe { readstat_sys::readstat_set_variable_handler(self.parser, variable_handler) };
97
98        debug!("After setting variable handler, error ==> {set_variable_handler_error}");
99
100        check_c_error(set_variable_handler_error as i32)?;
101        Ok(self)
102    }
103
104    /// Registers the callback invoked for each cell value during row parsing.
105    pub(crate) fn set_value_handler(
106        self,
107        value_handler: readstat_sys::readstat_value_handler,
108    ) -> Result<Self, ReadStatError> {
109        let set_value_handler_error =
110            unsafe { readstat_sys::readstat_set_value_handler(self.parser, value_handler) };
111
112        debug!("After setting value handler, error ==> {set_value_handler_error}");
113
114        check_c_error(set_value_handler_error as i32)?;
115        Ok(self)
116    }
117
118    /// Registers a custom handler for opening the data source.
119    pub(crate) fn set_open_handler(
120        self,
121        open_handler: readstat_sys::readstat_open_handler,
122    ) -> Result<Self, ReadStatError> {
123        let error = unsafe { readstat_sys::readstat_set_open_handler(self.parser, open_handler) };
124        debug!("After setting open handler, error ==> {error}");
125        check_c_error(error as i32)?;
126        Ok(self)
127    }
128
129    /// Registers a custom handler for closing the data source.
130    pub(crate) fn set_close_handler(
131        self,
132        close_handler: readstat_sys::readstat_close_handler,
133    ) -> Result<Self, ReadStatError> {
134        let error = unsafe { readstat_sys::readstat_set_close_handler(self.parser, close_handler) };
135        debug!("After setting close handler, error ==> {error}");
136        check_c_error(error as i32)?;
137        Ok(self)
138    }
139
140    /// Registers a custom handler for seeking within the data source.
141    pub(crate) fn set_seek_handler(
142        self,
143        seek_handler: readstat_sys::readstat_seek_handler,
144    ) -> Result<Self, ReadStatError> {
145        let error = unsafe { readstat_sys::readstat_set_seek_handler(self.parser, seek_handler) };
146        debug!("After setting seek handler, error ==> {error}");
147        check_c_error(error as i32)?;
148        Ok(self)
149    }
150
151    /// Registers a custom handler for reading from the data source.
152    pub(crate) fn set_read_handler(
153        self,
154        read_handler: readstat_sys::readstat_read_handler,
155    ) -> Result<Self, ReadStatError> {
156        let error = unsafe { readstat_sys::readstat_set_read_handler(self.parser, read_handler) };
157        debug!("After setting read handler, error ==> {error}");
158        check_c_error(error as i32)?;
159        Ok(self)
160    }
161
162    /// Registers a custom handler for progress updates.
163    pub(crate) fn set_update_handler(
164        self,
165        update_handler: readstat_sys::readstat_update_handler,
166    ) -> Result<Self, ReadStatError> {
167        let error =
168            unsafe { readstat_sys::readstat_set_update_handler(self.parser, update_handler) };
169        debug!("After setting update handler, error ==> {error}");
170        check_c_error(error as i32)?;
171        Ok(self)
172    }
173
174    /// Sets a custom I/O context pointer passed to all I/O handler callbacks.
175    pub(crate) fn set_io_ctx(self, io_ctx: *mut c_void) -> Result<Self, ReadStatError> {
176        let error = unsafe { readstat_sys::readstat_set_io_ctx(self.parser, io_ctx) };
177        debug!("After setting io ctx, error ==> {error}");
178        check_c_error(error as i32)?;
179        Ok(self)
180    }
181
182    /// Parses a `.sas7bdat` file, invoking registered callbacks as data is read.
183    ///
184    /// Returns the raw `ReadStat` error code. Use [`check_c_error`] to convert to a
185    /// `Result`.
186    pub(crate) fn parse_sas7bdat(
187        &mut self,
188        path: *const c_char,
189        user_ctx: *mut c_void,
190    ) -> readstat_sys::readstat_error_t {
191        let parse_sas7bdat_error: readstat_sys::readstat_error_t =
192            unsafe { readstat_sys::readstat_parse_sas7bdat(self.parser, path, user_ctx) };
193
194        debug!("After calling parse sas7bdat, error ==> {parse_sas7bdat_error}");
195
196        parse_sas7bdat_error
197    }
198}
199
200impl Drop for ReadStatParser {
201    fn drop(&mut self) {
202        debug!("Freeing parser");
203
204        unsafe { readstat_sys::readstat_parser_free(self.parser) };
205    }
206}