json_query/
jq.rs

1//! This module takes the unsafe bindings from `jq-sys` then (hopefully)
2//! wrapping to present a slightly safer API to use.
3//!
4//! These are building blocks and not intended for use from the public API.
5
6use super::Error;
7// Yeah, it's a lot.
8use jq_sys::{
9    jq_compile, jq_get_exit_code, jq_halted, jq_init, jq_next, jq_start, jq_state, jq_teardown, jv,
10    jv_copy, jv_dump_string, jv_free, jv_get_kind, jv_invalid_get_msg, jv_invalid_has_msg,
11    jv_kind_JV_KIND_INVALID, jv_kind_JV_KIND_NUMBER, jv_number_value, jv_parser, jv_parser_free,
12    jv_parser_new, jv_parser_next, jv_parser_set_buf, jv_string_value,
13};
14use std::ffi::{CStr, CString};
15use std::os::raw::c_char;
16
17pub struct Jq {
18    state: *mut jq_state,
19}
20
21impl Jq {
22    pub fn compile_program(program: CString) -> Result<Self, Error> {
23        let jq = Jq {
24            state: unsafe {
25                // jq's master branch shows this can be a null pointer, in
26                // which case the binary will exit with a `Error::System`.
27                let ptr = jq_init();
28                if ptr.is_null() {
29                    return Err(Error::System {
30                        msg: Some("Failed to init".into()),
31                    });
32                } else {
33                    ptr
34                }
35            },
36        };
37        unsafe {
38            if jq_compile(jq.state, program.as_ptr()) == 0 {
39                Err(Error::Compile)
40            } else {
41                Ok(jq)
42            }
43        }
44    }
45
46    fn is_halted(&self) -> bool {
47        unsafe { jq_halted(self.state) != 0 }
48    }
49
50    fn get_exit_code(&self) -> ExitCode {
51        let exit_code = JV {
52            ptr: unsafe { jq_get_exit_code(self.state) },
53        };
54
55        // The rules for this seem odd, but I'm trying to model this after the
56        // similar block in the jq `main.c`s `process()` function.
57
58        if exit_code.is_valid() {
59            ExitCode::JQ_OK
60        } else {
61            exit_code
62                .as_number()
63                .map(|i| (i as isize).into())
64                .unwrap_or(ExitCode::JQ_ERROR_UNKNOWN)
65        }
66    }
67
68    /// Run the jq program against an input.
69    pub fn execute(&mut self, input: CString) -> Result<String, Error> {
70        let mut parser = Parser::new();
71        self.process(parser.parse(input)?)
72    }
73
74    /// Unwind the parser and return the rendered result.
75    ///
76    /// When this results in `Err`, the String value should contain a message about
77    /// what failed.
78    fn process(&mut self, initial_value: JV) -> Result<String, Error> {
79        let mut buf = String::new();
80
81        unsafe {
82            jq_start(self.state, initial_value.ptr, 0);
83
84            dump(self, &mut buf)?;
85        }
86        // remove last trailing newline
87        let len = buf.trim_end().len();
88        buf.truncate(len);
89
90        Ok(buf)
91    }
92}
93
94impl Drop for Jq {
95    fn drop(&mut self) {
96        unsafe { jq_teardown(&mut self.state) }
97    }
98}
99
100struct JV {
101    ptr: jv,
102}
103
104impl JV {
105    /// Convert the current `JV` into the "dump string" rendering of itself.
106    pub fn as_dump_string(&self) -> Result<String, std::str::Utf8Error> {
107        let dump = JV {
108            ptr: unsafe { jv_dump_string(self.ptr, 0) },
109        };
110        unsafe { get_string_value(jv_string_value(dump.ptr)) }
111    }
112
113    /// Attempts to extract feedback from jq if the JV is invalid.
114    pub fn get_msg(&self) -> Option<String> {
115        if self.invalid_has_msg() {
116            let reason = {
117                let msg = JV {
118                    ptr: unsafe {
119                        // This call is gross since we're dipping outside of the
120                        // safe/drop-enabled wrapper to get a copy which will be freed
121                        // by jq. If we wrap it in a `JV`, we'll run into a double-free
122                        // situation.
123                        jv_invalid_get_msg(jv_copy(self.ptr))
124                    },
125                };
126
127                let s = unsafe { get_string_value(jv_string_value(msg.ptr)) };
128
129                format!("Parse error: {}", s.unwrap_or_else(|_| "unknown".into()))
130            };
131            Some(reason)
132        } else {
133            None
134        }
135    }
136
137    pub fn as_number(&self) -> Option<f64> {
138        unsafe {
139            if jv_get_kind(self.ptr) == jv_kind_JV_KIND_NUMBER {
140                Some(jv_number_value(self.ptr))
141            } else {
142                None
143            }
144        }
145    }
146
147    pub fn is_valid(&self) -> bool {
148        unsafe {
149            // FIXME: looks like this copy should not be needed (but it is?)
150            //  Test suite shows a memory error if this value is freed after
151            //  being passed to `jv_get_kind()`, so I guess this is a
152            //  consuming call?
153
154            jv_get_kind(jv_copy(self.ptr)) != jv_kind_JV_KIND_INVALID
155        }
156    }
157
158    pub fn invalid_has_msg(&self) -> bool {
159        // FIXME: the C lib suggests the jv passed in here will eventually be freed.
160        //  I had a a `jv_copy()` to side-step this, but removing it removes one
161        //  leak warning in valgrind, so I don't know what the deal is.
162        unsafe { jv_invalid_has_msg(self.ptr) == 1 }
163    }
164}
165
166impl Drop for JV {
167    fn drop(&mut self) {
168        unsafe { jv_free(self.ptr) };
169    }
170}
171
172struct Parser {
173    ptr: *mut jv_parser,
174}
175
176impl Parser {
177    pub fn new() -> Self {
178        Self {
179            ptr: unsafe { jv_parser_new(0) },
180        }
181    }
182
183    pub fn parse(&mut self, input: CString) -> Result<JV, Error> {
184        // For a single run, we could set this to `1` (aka `true`) but this will
185        // break the repeated `JqProgram` usage.
186        // It may be worth exposing this to the caller so they can set it for each
187        // use case, but for now we'll just "leave it open."
188        let is_last = 0;
189
190        // Originally I planned to have a separate "set_buf" method, but it looks like
191        // the C api really wants you to set the buffer, then call `jv_parser_next()` in
192        // the same logical block.
193        // Mainly I think the important thing is to ensure the `input` outlives both the
194        // set_buf and next calls.
195        unsafe {
196            jv_parser_set_buf(
197                self.ptr,
198                input.as_ptr(),
199                input.as_bytes().len() as i32,
200                is_last,
201            )
202        };
203
204        let value = JV {
205            ptr: unsafe { jv_parser_next(self.ptr) },
206        };
207        if value.is_valid() {
208            Ok(value)
209        } else {
210            Err(Error::System {
211                msg: Some(
212                    value
213                        .get_msg()
214                        .unwrap_or_else(|| "Parser error".to_string()),
215                ),
216            })
217        }
218    }
219}
220
221impl Drop for Parser {
222    fn drop(&mut self) {
223        unsafe {
224            jv_parser_free(self.ptr);
225        }
226    }
227}
228
229/// Takes a pointer to a nul term string, and attempts to convert it to a String.
230unsafe fn get_string_value(value: *const c_char) -> Result<String, std::str::Utf8Error> {
231    let s = CStr::from_ptr(value).to_str()?;
232    Ok(s.to_owned())
233}
234
235/// Renders the data from the parser and pushes it into the buffer.
236unsafe fn dump(jq: &Jq, buf: &mut String) -> Result<(), Error> {
237    // Looks a lot like an iterator...
238
239    let mut value = JV {
240        ptr: jq_next(jq.state),
241    };
242
243    while value.is_valid() {
244        match value.as_dump_string() {
245            Ok(s) => {
246                buf.push_str(&s);
247                buf.push('\n');
248            }
249            Err(e) => {
250                return Err(Error::System {
251                    msg: Some(format!("String Decode error: {}", e)),
252                });
253            }
254        };
255
256        value = JV {
257            ptr: jq_next(jq.state),
258        };
259    }
260
261    if jq.is_halted() {
262        use self::ExitCode::*;
263        match jq.get_exit_code() {
264            JQ_ERROR_SYSTEM => Err(Error::System {
265                msg: value.get_msg(),
266            }),
267            // As far as I know, we should not be able to see a compile error
268            // this deep into the execution of a jq program (it would need to be
269            // compiled already, right?)
270            // Still, compile failure is represented by an exit code, so in
271            // order to be exhaustive we have to check for it.
272            JQ_ERROR_COMPILE => Err(Error::Compile),
273            // Any of these `OK_` variants are "success" cases.
274            // I suppose the jq program can halt successfully, or not, or not at
275            // all and still terminate some other way?
276            JQ_OK | JQ_OK_NULL_KIND | JQ_OK_NO_OUTPUT => Ok(()),
277            JQ_ERROR_UNKNOWN => Err(Error::Unknown),
278        }
279    } else if let Some(reason) = value.get_msg() {
280        Err(Error::System { msg: Some(reason) })
281    } else {
282        Ok(())
283    }
284}
285
286/// Various exit codes jq checks for during the `if (jq_halted(jq))` branch of
287/// their processing loop.
288///
289/// Adapted from the enum seen in jq's master branch right now.
290/// The numbers seem to line up with the magic numbers seen in
291/// the 1.6 release, though there's no enum that I saw at that point in the git
292/// history.
293#[allow(non_camel_case_types, dead_code)]
294enum ExitCode {
295    JQ_OK = 0,
296    JQ_OK_NULL_KIND = -1,
297    JQ_ERROR_SYSTEM = 2,
298    JQ_ERROR_COMPILE = 3,
299    JQ_OK_NO_OUTPUT = -4,
300    JQ_ERROR_UNKNOWN = 5,
301}
302
303impl From<isize> for ExitCode {
304    fn from(number: isize) -> Self {
305        use self::ExitCode::*;
306        match number as isize {
307            n if n == JQ_OK as isize => JQ_OK,
308            n if n == JQ_OK_NULL_KIND as isize => JQ_OK_NULL_KIND,
309            n if n == JQ_ERROR_SYSTEM as isize => JQ_ERROR_SYSTEM,
310            n if n == JQ_ERROR_COMPILE as isize => JQ_ERROR_COMPILE,
311            n if n == JQ_OK_NO_OUTPUT as isize => JQ_OK_NO_OUTPUT,
312            n if n == JQ_ERROR_UNKNOWN as isize => JQ_ERROR_UNKNOWN,
313            // `5` is called out explicitly in the jq source, but also "unknown"
314            // seems to make good sense for other unexpected number.
315            _ => JQ_ERROR_UNKNOWN,
316        }
317    }
318}