#![deny(missing_docs)]
extern crate jq_sys;
#[cfg(test)]
#[macro_use]
extern crate serde_json;
mod errors;
mod jq;
use std::ffi::CString;
pub use errors::{Error, Result};
pub fn run(program: &str, data: &str) -> Result<String> {
compile(program)?.run(data)
}
pub struct JqProgram {
jq: jq::Jq,
}
impl JqProgram {
pub fn run(&mut self, data: &str) -> Result<String> {
if data.trim().is_empty() {
return Ok("".into());
}
let input = CString::new(data)?;
self.jq.execute(input)
}
}
pub fn compile(program: &str) -> Result<JqProgram> {
let prog = CString::new(program)?;
Ok(JqProgram {
jq: jq::Jq::compile_program(prog)?,
})
}
#[cfg(test)]
mod test {
use super::{compile, run, Error};
use matches::assert_matches;
use serde_json;
#[test]
fn reuse_compiled_program() {
let query = r#"if . == 0 then "zero" elif . == 1 then "one" else "many" end"#;
let mut prog = compile(&query).unwrap();
assert_eq!(prog.run("2").unwrap(), "\"many\"\n");
assert_eq!(prog.run("1").unwrap(), "\"one\"\n");
assert_eq!(prog.run("0").unwrap(), "\"zero\"\n");
}
#[test]
fn jq_state_is_not_global() {
let input = r#"{"id": 123, "name": "foo"}"#;
let query1 = r#".name"#;
let query2 = r#".id"#;
let mut prog1 = compile(&query1).unwrap();
let mut prog2 = compile(&query2).unwrap();
assert_eq!(prog1.run(input).unwrap(), "\"foo\"\n");
assert_eq!(prog2.run(input).unwrap(), "123\n");
assert_eq!(prog1.run(input).unwrap(), "\"foo\"\n");
assert_eq!(prog2.run(input).unwrap(), "123\n");
}
fn get_movies() -> serde_json::Value {
json!({
"movies": [
{ "title": "Coraline", "year": 2009 },
{ "title": "ParaNorman", "year": 2012 },
{ "title": "Boxtrolls", "year": 2014 },
{ "title": "Kubo and the Two Strings", "year": 2016 },
{ "title": "Missing Link", "year": 2019 }
]
})
}
#[test]
fn identity_nothing() {
assert_eq!(run(".", "").unwrap(), "".to_string());
}
#[test]
fn identity_empty() {
assert_eq!(run(".", "{}").unwrap(), "{}\n".to_string());
}
#[test]
fn extract_dates() {
let data = get_movies();
let query = "[.movies[].year]";
let output = run(query, &data.to_string()).unwrap();
let parsed: Vec<i64> = serde_json::from_str(&output).unwrap();
assert_eq!(vec![2009, 2012, 2014, 2016, 2019], parsed);
}
#[test]
fn extract_name() {
let res = run(".name", r#"{"name": "test"}"#);
assert_eq!(res.unwrap(), "\"test\"\n".to_string());
}
#[test]
fn unpack_array() {
let res = run(".[]", "[1,2,3]");
assert_eq!(res.unwrap(), "1\n2\n3\n".to_string());
}
#[test]
fn compile_error() {
let res = run(". aa12312me dsaafsdfsd", "{\"name\": \"test\"}");
assert_matches!(res, Err(Error::InvalidProgram));
}
#[test]
fn parse_error() {
let res = run(".", "{1233 invalid json ahoy : est\"}");
assert_matches!(res, Err(Error::System { .. }));
}
#[test]
fn just_open_brace() {
let res = run(".", "{");
assert_matches!(res, Err(Error::System { .. }));
}
#[test]
fn just_close_brace() {
let res = run(".", "}");
assert_matches!(res, Err(Error::System { .. }));
}
#[test]
fn total_garbage() {
let data = r#"
{
moreLike: "an object literal but also bad"
loveToDangleComma: true,
}"#;
let res = run(".", data);
assert_matches!(res, Err(Error::System { .. }));
}
pub mod mem_errors {
use super::*;
#[test]
fn missing_field_access() {
let prog = ".[] | .hello";
let data = "[1,2,3]";
let res = run(prog, data);
assert_matches!(res, Err(Error::System { .. }));
}
#[test]
fn missing_field_access_compiled() {
let mut prog = compile(".[] | .hello").unwrap();
let data = "[1,2,3]";
let res = prog.run(data);
assert_matches!(res, Err(Error::System { .. }));
}
}
}