#![deny(
clippy::all,
clippy::cargo,
clippy::nursery,
clippy::pedantic,
deprecated_in_future,
future_incompatible,
missing_docs,
nonstandard_style,
rust_2018_idioms,
rustdoc,
warnings,
unused_results,
unused_qualifications,
unused_lifetimes,
unused_import_braces,
unsafe_code,
unreachable_pub,
trivial_casts,
trivial_numeric_casts,
missing_debug_implementations,
missing_copy_implementations
)]
#![warn(variant_size_differences)]
#![allow(clippy::multiple_crate_versions, missing_doc_code_examples)]
#![doc(html_root_url = "https://docs.rs/automaat-processor-json-edit/0.1.0")]
use automaat_core::{Context, Processor};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{error, fmt};
#[cfg_attr(feature = "juniper", derive(juniper::GraphQLObject))]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct JsonEdit {
pub json: String,
pub program: String,
pub pretty_output: bool,
}
#[cfg(feature = "juniper")]
#[graphql(name = "StringRegexInput")]
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, juniper::GraphQLInputObject)]
pub struct Input {
json: String,
program: String,
pretty_output: Option<bool>,
}
#[cfg(feature = "juniper")]
impl From<Input> for JsonEdit {
fn from(input: Input) -> Self {
Self {
json: input.json,
program: input.program,
pretty_output: input.pretty_output.unwrap_or(false),
}
}
}
impl JsonEdit {
fn to_string(&self, value: &Value) -> Result<String, serde_json::Error> {
if value.is_string() {
return Ok(value.as_str().unwrap().to_owned());
};
if self.pretty_output {
serde_json::to_string_pretty(&value)
} else {
serde_json::to_string(&value)
}
}
}
impl<'a> Processor<'a> for JsonEdit {
const NAME: &'static str = "JSON Edit";
type Error = Error;
type Output = String;
fn validate(&self) -> Result<(), Self::Error> {
json_query::compile(self.program.as_str())
.map(|_| ())
.map_err(Into::into)
}
fn run(&self, _context: &Context) -> Result<Option<Self::Output>, Self::Error> {
let mut output = vec![];
let json = json_query::run(self.program.as_str(), self.json.as_str())?;
for line in json.lines() {
let value: Value = serde_json::from_str(line)?;
if !value.is_null() {
output.push(self.to_string(&value)?)
}
}
let string = output.join("\n").trim().to_owned();
if string.is_empty() {
Ok(None)
} else {
Ok(Some(string))
}
}
}
#[derive(Debug)]
pub enum Error {
Json(String),
Serde(serde_json::Error),
#[doc(hidden)]
__Unknown, }
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::Json(ref err) => write!(f, "JSON error: {}", err),
Error::Serde(ref err) => write!(f, "Serde error: {}", err),
Error::__Unknown => unreachable!(),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
Error::Json(_) => None,
Error::Serde(ref err) => Some(err),
Error::__Unknown => unreachable!(),
}
}
}
impl From<String> for Error {
fn from(err: String) -> Self {
Error::Json(err)
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Serde(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn processor_stub() -> JsonEdit {
JsonEdit {
json: r#"{"hello":"world"}"#.to_owned(),
program: ".hello".to_owned(),
pretty_output: false,
}
}
mod run {
use super::*;
#[test]
fn test_mismatch_program() {
let mut processor = processor_stub();
processor.json = r#"{"hello":"world"}"#.to_owned();
processor.program = ".hi".to_owned();
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap();
assert!(output.is_none())
}
#[test]
fn test_match_program() {
let mut processor = processor_stub();
processor.json = r#"{"hello":"world"}"#.to_owned();
processor.program = ".hello".to_owned();
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap().expect("Some");
assert_eq!(output, "world".to_owned())
}
#[test]
fn test_unwrapped_array() {
let mut processor = processor_stub();
processor.json = r#"[{"hello":"world"},{"hello":2}]"#.to_owned();
processor.program = ".[] | .hello".to_owned();
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap().expect("Some");
assert_eq!(output, "world\n2".to_owned())
}
#[test]
fn test_empty_output() {
let mut processor = processor_stub();
processor.json = r#"[{"hello":"world"},{"hello":2}]"#.to_owned();
processor.program = ".[0]".to_owned();
processor.pretty_output = true;
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap().expect("Some");
assert_eq!(output, "{\n \"hello\": \"world\"\n}".to_owned())
}
#[test]
fn test_combination_of_empty_and_non_empty_lines() {
let mut processor = processor_stub();
processor.json = r#"["","hello","","world"]"#.to_owned();
processor.program = ".[]".to_owned();
processor.pretty_output = true;
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap().expect("Some");
assert_eq!(output, "hello\n\nworld".to_owned())
}
#[test]
fn test_combination_of_null_and_non_null_lines() {
let mut processor = processor_stub();
processor.json = r#"[null,"hello",null,"world"]"#.to_owned();
processor.program = ".[]".to_owned();
processor.pretty_output = true;
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap().expect("Some");
assert_eq!(output, "hello\nworld".to_owned())
}
#[test]
fn test_empty_string_output() {
let mut processor = processor_stub();
processor.json = r#"["",""]"#.to_owned();
processor.program = ".[]".to_owned();
processor.pretty_output = true;
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap();
assert!(output.is_none())
}
#[test]
fn test_null_output() {
let mut processor = processor_stub();
processor.json = r#"{"hello":"world"}"#.to_owned();
processor.program = ".hi".to_owned();
processor.pretty_output = true;
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap();
assert!(output.is_none())
}
#[test]
fn test_pretty_output_multi_line() {
let mut processor = processor_stub();
processor.json = r#"[{"hello":"world"},{"hello":2}]"#.to_owned();
processor.program = ".[]".to_owned();
processor.pretty_output = true;
let context = Context::new().unwrap();
let output = processor.run(&context).unwrap().expect("Some");
assert_eq!(
output,
"{\n \"hello\": \"world\"\n}\n{\n \"hello\": 2\n}".to_owned()
)
}
}
mod validate {
use super::*;
#[test]
fn test_valid_syntax() {
let mut processor = processor_stub();
processor.program = r".hello".to_owned();
processor.validate().unwrap()
}
#[test]
#[should_panic]
fn test_invalid_syntax() {
let mut processor = processor_stub();
processor.program = r"..hello \NO".to_owned();
processor.validate().unwrap()
}
}
#[test]
fn test_readme_deps() {
version_sync::assert_markdown_deps_updated!("README.md");
}
#[test]
fn test_html_root_url() {
version_sync::assert_html_root_url_updated!("src/lib.rs");
}
}