use super::Error;
use jq_sys::{
jq_compile, jq_get_exit_code, jq_halted, jq_init, jq_next, jq_start, jq_state, jq_teardown, jv,
jv_copy, jv_dump_string, jv_free, jv_get_kind, jv_invalid_get_msg, jv_invalid_has_msg,
jv_kind_JV_KIND_INVALID, jv_kind_JV_KIND_NUMBER, jv_number_value, jv_parser, jv_parser_free,
jv_parser_new, jv_parser_next, jv_parser_set_buf, jv_string_value,
};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
pub struct Jq {
state: *mut jq_state,
}
impl Jq {
pub fn compile_program(program: CString) -> Result<Self, Error> {
let jq = Jq {
state: unsafe {
let ptr = jq_init();
if ptr.is_null() {
return Err(Error::System {
msg: Some("Failed to init".into()),
});
} else {
ptr
}
},
};
unsafe {
if jq_compile(jq.state, program.as_ptr()) == 0 {
Err(Error::Compile)
} else {
Ok(jq)
}
}
}
fn is_halted(&self) -> bool {
unsafe { jq_halted(self.state) != 0 }
}
fn get_exit_code(&self) -> ExitCode {
let exit_code = JV {
ptr: unsafe { jq_get_exit_code(self.state) },
};
if exit_code.is_valid() {
ExitCode::JQ_OK
} else {
exit_code
.as_number()
.map(|i| (i as isize).into())
.unwrap_or(ExitCode::JQ_ERROR_UNKNOWN)
}
}
pub fn execute(&mut self, input: CString) -> Result<String, Error> {
let mut parser = Parser::new();
self.process(parser.parse(input)?)
}
fn process(&mut self, initial_value: JV) -> Result<String, Error> {
let mut buf = String::new();
unsafe {
jq_start(self.state, initial_value.ptr, 0);
dump(self, &mut buf)?;
}
let len = buf.trim_end().len();
buf.truncate(len);
Ok(buf)
}
}
impl Drop for Jq {
fn drop(&mut self) {
unsafe { jq_teardown(&mut self.state) }
}
}
struct JV {
ptr: jv,
}
impl JV {
pub fn as_dump_string(&self) -> Result<String, std::str::Utf8Error> {
let dump = JV {
ptr: unsafe { jv_dump_string(self.ptr, 0) },
};
unsafe { get_string_value(jv_string_value(dump.ptr)) }
}
pub fn get_msg(&self) -> Option<String> {
if self.invalid_has_msg() {
let reason = {
let msg = JV {
ptr: unsafe {
jv_invalid_get_msg(jv_copy(self.ptr))
},
};
let s = unsafe { get_string_value(jv_string_value(msg.ptr)) };
format!("Parse error: {}", s.unwrap_or_else(|_| "unknown".into()))
};
Some(reason)
} else {
None
}
}
pub fn as_number(&self) -> Option<f64> {
unsafe {
if jv_get_kind(self.ptr) == jv_kind_JV_KIND_NUMBER {
Some(jv_number_value(self.ptr))
} else {
None
}
}
}
pub fn is_valid(&self) -> bool {
unsafe {
jv_get_kind(jv_copy(self.ptr)) != jv_kind_JV_KIND_INVALID
}
}
pub fn invalid_has_msg(&self) -> bool {
unsafe { jv_invalid_has_msg(self.ptr) == 1 }
}
}
impl Drop for JV {
fn drop(&mut self) {
unsafe { jv_free(self.ptr) };
}
}
struct Parser {
ptr: *mut jv_parser,
}
impl Parser {
pub fn new() -> Self {
Self {
ptr: unsafe { jv_parser_new(0) },
}
}
pub fn parse(&mut self, input: CString) -> Result<JV, Error> {
let is_last = 0;
unsafe {
jv_parser_set_buf(
self.ptr,
input.as_ptr(),
input.as_bytes().len() as i32,
is_last,
)
};
let value = JV {
ptr: unsafe { jv_parser_next(self.ptr) },
};
if value.is_valid() {
Ok(value)
} else {
Err(Error::System {
msg: Some(
value
.get_msg()
.unwrap_or_else(|| "Parser error".to_string()),
),
})
}
}
}
impl Drop for Parser {
fn drop(&mut self) {
unsafe {
jv_parser_free(self.ptr);
}
}
}
unsafe fn get_string_value(value: *const c_char) -> Result<String, std::str::Utf8Error> {
let s = CStr::from_ptr(value).to_str()?;
Ok(s.to_owned())
}
unsafe fn dump(jq: &Jq, buf: &mut String) -> Result<(), Error> {
let mut value = JV {
ptr: jq_next(jq.state),
};
while value.is_valid() {
match value.as_dump_string() {
Ok(s) => {
buf.push_str(&s);
buf.push('\n');
}
Err(e) => {
return Err(Error::System {
msg: Some(format!("String Decode error: {}", e)),
});
}
};
value = JV {
ptr: jq_next(jq.state),
};
}
if jq.is_halted() {
use self::ExitCode::*;
match jq.get_exit_code() {
JQ_ERROR_SYSTEM => Err(Error::System {
msg: value.get_msg(),
}),
JQ_ERROR_COMPILE => Err(Error::Compile),
JQ_OK | JQ_OK_NULL_KIND | JQ_OK_NO_OUTPUT => Ok(()),
JQ_ERROR_UNKNOWN => Err(Error::Unknown),
}
} else if let Some(reason) = value.get_msg() {
Err(Error::System { msg: Some(reason) })
} else {
Ok(())
}
}
#[allow(non_camel_case_types, dead_code)]
enum ExitCode {
JQ_OK = 0,
JQ_OK_NULL_KIND = -1,
JQ_ERROR_SYSTEM = 2,
JQ_ERROR_COMPILE = 3,
JQ_OK_NO_OUTPUT = -4,
JQ_ERROR_UNKNOWN = 5,
}
impl From<isize> for ExitCode {
fn from(number: isize) -> Self {
use self::ExitCode::*;
match number as isize {
n if n == JQ_OK as isize => JQ_OK,
n if n == JQ_OK_NULL_KIND as isize => JQ_OK_NULL_KIND,
n if n == JQ_ERROR_SYSTEM as isize => JQ_ERROR_SYSTEM,
n if n == JQ_ERROR_COMPILE as isize => JQ_ERROR_COMPILE,
n if n == JQ_OK_NO_OUTPUT as isize => JQ_OK_NO_OUTPUT,
n if n == JQ_ERROR_UNKNOWN as isize => JQ_ERROR_UNKNOWN,
_ => JQ_ERROR_UNKNOWN,
}
}
}