#![deprecated(
since = "0.3.1",
note = "This is the final release of `json-query`. Future releases will be published as `jq-rs`."
)]
extern crate jq_sys;
#[cfg(test)]
#[macro_use]
extern crate serde_json;
mod jq;
use std::ffi::CString;
use std::fmt;
pub enum Error {
Compile,
System {
msg: Option<String>,
},
Unknown,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let detail: String = match self {
Error::Compile => "syntax error: JQ Program failed to compile.".into(),
Error::System { msg } => msg
.as_ref()
.cloned()
.unwrap_or_else(|| "Unknown JQ Error".into()),
Error::Unknown => "Unknown JQ Error.".into(),
};
write!(f, "{}", detail)
}
}
pub fn run(program: &str, data: &str) -> Result<String, String> {
compile(program)?.run(data)
}
pub struct JqProgram {
jq: jq::Jq,
}
impl JqProgram {
pub fn run(&mut self, data: &str) -> Result<String, String> {
if data.trim().is_empty() {
return Ok("".into());
}
let input =
CString::new(data).map_err(|_| "unable to convert data to c string.".to_string())?;
self.jq.execute(input).map_err(|e| {
format!("{}", e)
})
}
}
pub fn compile(program: &str) -> Result<JqProgram, String> {
let prog =
CString::new(program).map_err(|_| "unable to convert data to c string.".to_string())?;
Ok(JqProgram {
jq: jq::Jq::compile_program(prog).map_err(|e| {
format!("{}", e)
})?,
})
}
#[cfg(test)]
mod test {
use super::{compile, run};
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(), r#""many""#);
assert_eq!(prog.run("1").unwrap(), r#""one""#);
assert_eq!(prog.run("0").unwrap(), r#""zero""#);
}
#[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(), r#""foo""#);
assert_eq!(prog2.run(input).unwrap(), r#"123"#);
assert_eq!(prog1.run(input).unwrap(), r#""foo""#);
assert_eq!(prog2.run(input).unwrap(), r#"123"#);
}
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(".", ""), Ok("".to_string()));
}
#[test]
fn identity_empty() {
assert_eq!(run(".", "{}"), Ok("{}".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, Ok(r#""test""#.to_string()));
}
#[test]
fn unpack_array() {
let res = run(".[]", "[1,2,3]");
assert_eq!(res, Ok("1\n2\n3".to_string()));
}
#[test]
fn compile_error() {
let res = run(". aa12312me dsaafsdfsd", "{\"name\": \"test\"}");
assert!(res.is_err());
}
#[test]
fn parse_error() {
let res = run(".", "{1233 invalid json ahoy : est\"}");
assert!(res.is_err());
}
#[test]
fn just_open_brace() {
let res = run(".", "{");
assert!(res.is_err());
}
#[test]
fn just_close_brace() {
let res = run(".", "}");
assert!(res.is_err());
}
#[test]
fn total_garbage() {
let data = r#"
{
moreLike: "an object literal but also bad"
loveToDangleComma: true,
}"#;
let res = run(".", data);
assert!(res.is_err());
}
pub mod mem_errors {
use super::*;
#[test]
fn missing_field_access() {
let prog = ".[] | .hello";
let data = "[1,2,3]";
assert!(run(prog, data).is_err());
}
#[test]
fn missing_field_access_compiled() {
let mut prog = compile(".[] | .hello").unwrap();
let data = "[1,2,3]";
assert!(prog.run(data).is_err());
}
}
}