automaat_processor_json_edit/
lib.rs1#![deny(
42 clippy::all,
43 clippy::cargo,
44 clippy::nursery,
45 clippy::pedantic,
46 deprecated_in_future,
47 future_incompatible,
48 missing_docs,
49 nonstandard_style,
50 rust_2018_idioms,
51 rustdoc,
52 warnings,
53 unused_results,
54 unused_qualifications,
55 unused_lifetimes,
56 unused_import_braces,
57 unsafe_code,
58 unreachable_pub,
59 trivial_casts,
60 trivial_numeric_casts,
61 missing_debug_implementations,
62 missing_copy_implementations
63)]
64#![warn(variant_size_differences)]
65#![allow(clippy::multiple_crate_versions, missing_doc_code_examples)]
66#![doc(html_root_url = "https://docs.rs/automaat-processor-json-edit/0.1.0")]
67
68use automaat_core::{Context, Processor};
69use serde::{Deserialize, Serialize};
70use serde_json::Value;
71use std::{error, fmt};
72
73#[cfg_attr(feature = "juniper", derive(juniper::GraphQLObject))]
75#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
76pub struct JsonEdit {
77 pub json: String,
79
80 pub program: String,
89
90 pub pretty_output: bool,
95}
96
97#[cfg(feature = "juniper")]
106#[graphql(name = "StringRegexInput")]
107#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, juniper::GraphQLInputObject)]
108pub struct Input {
109 json: String,
110 program: String,
111 pretty_output: Option<bool>,
112}
113
114#[cfg(feature = "juniper")]
115impl From<Input> for JsonEdit {
116 fn from(input: Input) -> Self {
117 Self {
118 json: input.json,
119 program: input.program,
120 pretty_output: input.pretty_output.unwrap_or(false),
121 }
122 }
123}
124
125impl JsonEdit {
126 fn to_string(&self, value: &Value) -> Result<String, serde_json::Error> {
127 if value.is_string() {
128 return Ok(value.as_str().unwrap().to_owned());
129 };
130
131 if self.pretty_output {
132 serde_json::to_string_pretty(&value)
133 } else {
134 serde_json::to_string(&value)
135 }
136 }
137}
138
139impl<'a> Processor<'a> for JsonEdit {
140 const NAME: &'static str = "JSON Edit";
141
142 type Error = Error;
143 type Output = String;
144
145 fn validate(&self) -> Result<(), Self::Error> {
152 json_query::compile(self.program.as_str())
153 .map(|_| ())
154 .map_err(Into::into)
155 }
156
157 fn run(&self, _context: &Context) -> Result<Option<Self::Output>, Self::Error> {
222 let mut output = vec![];
223 let json = json_query::run(self.program.as_str(), self.json.as_str())?;
224
225 for line in json.lines() {
228 let value: Value = serde_json::from_str(line)?;
229
230 if !value.is_null() {
231 output.push(self.to_string(&value)?)
232 }
233 }
234
235 let string = output.join("\n").trim().to_owned();
236
237 if string.is_empty() {
238 Ok(None)
239 } else {
240 Ok(Some(string))
241 }
242 }
243}
244
245#[derive(Debug)]
250pub enum Error {
251 Json(String),
253
254 Serde(serde_json::Error),
256
257 #[doc(hidden)]
258 __Unknown, }
260
261impl fmt::Display for Error {
262 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263 match *self {
264 Error::Json(ref err) => write!(f, "JSON error: {}", err),
265 Error::Serde(ref err) => write!(f, "Serde error: {}", err),
266 Error::__Unknown => unreachable!(),
267 }
268 }
269}
270
271impl error::Error for Error {
272 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
273 match *self {
274 Error::Json(_) => None,
275 Error::Serde(ref err) => Some(err),
276 Error::__Unknown => unreachable!(),
277 }
278 }
279}
280
281impl From<String> for Error {
282 fn from(err: String) -> Self {
283 Error::Json(err)
284 }
285}
286
287impl From<serde_json::Error> for Error {
288 fn from(err: serde_json::Error) -> Self {
289 Error::Serde(err)
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 fn processor_stub() -> JsonEdit {
298 JsonEdit {
299 json: r#"{"hello":"world"}"#.to_owned(),
300 program: ".hello".to_owned(),
301 pretty_output: false,
302 }
303 }
304
305 mod run {
306 use super::*;
307
308 #[test]
309 fn test_mismatch_program() {
310 let mut processor = processor_stub();
311 processor.json = r#"{"hello":"world"}"#.to_owned();
312 processor.program = ".hi".to_owned();
313
314 let context = Context::new().unwrap();
315 let output = processor.run(&context).unwrap();
316
317 assert!(output.is_none())
318 }
319
320 #[test]
321 fn test_match_program() {
322 let mut processor = processor_stub();
323 processor.json = r#"{"hello":"world"}"#.to_owned();
324 processor.program = ".hello".to_owned();
325
326 let context = Context::new().unwrap();
327 let output = processor.run(&context).unwrap().expect("Some");
328
329 assert_eq!(output, "world".to_owned())
330 }
331
332 #[test]
333 fn test_unwrapped_array() {
334 let mut processor = processor_stub();
335 processor.json = r#"[{"hello":"world"},{"hello":2}]"#.to_owned();
336 processor.program = ".[] | .hello".to_owned();
337
338 let context = Context::new().unwrap();
339 let output = processor.run(&context).unwrap().expect("Some");
340
341 assert_eq!(output, "world\n2".to_owned())
342 }
343
344 #[test]
345 fn test_empty_output() {
346 let mut processor = processor_stub();
347 processor.json = r#"[{"hello":"world"},{"hello":2}]"#.to_owned();
348 processor.program = ".[0]".to_owned();
349 processor.pretty_output = true;
350
351 let context = Context::new().unwrap();
352 let output = processor.run(&context).unwrap().expect("Some");
353
354 assert_eq!(output, "{\n \"hello\": \"world\"\n}".to_owned())
355 }
356
357 #[test]
358 fn test_combination_of_empty_and_non_empty_lines() {
359 let mut processor = processor_stub();
360 processor.json = r#"["","hello","","world"]"#.to_owned();
361 processor.program = ".[]".to_owned();
362 processor.pretty_output = true;
363
364 let context = Context::new().unwrap();
365 let output = processor.run(&context).unwrap().expect("Some");
366
367 assert_eq!(output, "hello\n\nworld".to_owned())
372 }
373
374 #[test]
375 fn test_combination_of_null_and_non_null_lines() {
376 let mut processor = processor_stub();
377 processor.json = r#"[null,"hello",null,"world"]"#.to_owned();
378 processor.program = ".[]".to_owned();
379 processor.pretty_output = true;
380
381 let context = Context::new().unwrap();
382 let output = processor.run(&context).unwrap().expect("Some");
383
384 assert_eq!(output, "hello\nworld".to_owned())
385 }
386
387 #[test]
388 fn test_empty_string_output() {
389 let mut processor = processor_stub();
390 processor.json = r#"["",""]"#.to_owned();
391 processor.program = ".[]".to_owned();
392 processor.pretty_output = true;
393
394 let context = Context::new().unwrap();
395 let output = processor.run(&context).unwrap();
396
397 assert!(output.is_none())
398 }
399
400 #[test]
401 fn test_null_output() {
402 let mut processor = processor_stub();
403 processor.json = r#"{"hello":"world"}"#.to_owned();
404 processor.program = ".hi".to_owned();
405 processor.pretty_output = true;
406
407 let context = Context::new().unwrap();
408 let output = processor.run(&context).unwrap();
409
410 assert!(output.is_none())
411 }
412
413 #[test]
414 fn test_pretty_output_multi_line() {
415 let mut processor = processor_stub();
416 processor.json = r#"[{"hello":"world"},{"hello":2}]"#.to_owned();
417 processor.program = ".[]".to_owned();
418 processor.pretty_output = true;
419
420 let context = Context::new().unwrap();
421 let output = processor.run(&context).unwrap().expect("Some");
422
423 assert_eq!(
424 output,
425 "{\n \"hello\": \"world\"\n}\n{\n \"hello\": 2\n}".to_owned()
426 )
427 }
428 }
429
430 mod validate {
431 use super::*;
432
433 #[test]
434 fn test_valid_syntax() {
435 let mut processor = processor_stub();
436 processor.program = r".hello".to_owned();
437
438 processor.validate().unwrap()
439 }
440
441 #[test]
442 #[should_panic]
443 fn test_invalid_syntax() {
444 let mut processor = processor_stub();
445 processor.program = r"..hello \NO".to_owned();
446
447 processor.validate().unwrap()
448 }
449 }
450
451 #[test]
452 fn test_readme_deps() {
453 version_sync::assert_markdown_deps_updated!("README.md");
454 }
455
456 #[test]
457 fn test_html_root_url() {
458 version_sync::assert_html_root_url_updated!("src/lib.rs");
459 }
460}