use serde::{Deserialize, Serialize};
use std::path::Path;
use crate::output::{EvaluationResult, MatchResult};
use crate::parser::ast::Value;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct JsonMatchResult {
pub text: String,
pub offset: usize,
pub value: String,
pub tags: Vec<String>,
pub score: u8,
}
impl JsonMatchResult {
#[must_use]
pub fn from_match_result(match_result: &MatchResult) -> Self {
Self {
text: match_result.message.clone(),
offset: match_result.offset,
value: format_value_as_hex(&match_result.value),
tags: match_result.rule_path.clone(),
score: match_result.confidence,
}
}
#[must_use]
pub fn new(text: String, offset: usize, value: String, tags: Vec<String>, score: u8) -> Self {
Self {
text,
offset,
value,
tags,
score: score.min(100), }
}
pub fn add_tag(&mut self, tag: String) {
self.tags.push(tag);
}
pub fn set_score(&mut self, score: u8) {
self.score = score.min(100);
}
}
#[must_use]
pub fn format_value_as_hex(value: &Value) -> String {
use std::fmt::Write;
match value {
Value::Bytes(bytes) => {
let mut result = String::with_capacity(bytes.len() * 2);
for &b in bytes {
write!(&mut result, "{b:02x}").expect("Writing to String should never fail");
}
result
}
Value::String(s) => {
let bytes = s.as_bytes();
let mut result = String::with_capacity(bytes.len() * 2);
for &b in bytes {
write!(&mut result, "{b:02x}").expect("Writing to String should never fail");
}
result
}
Value::Uint(n) => {
let bytes = n.to_le_bytes();
let mut result = String::with_capacity(16); for &b in &bytes {
write!(&mut result, "{b:02x}").expect("Writing to String should never fail");
}
result
}
Value::Int(n) => {
let bytes = n.to_le_bytes();
let mut result = String::with_capacity(16); for &b in &bytes {
write!(&mut result, "{b:02x}").expect("Writing to String should never fail");
}
result
}
Value::Float(f) => {
let bytes = f.to_le_bytes();
let mut result = String::with_capacity(16); for &b in &bytes {
write!(&mut result, "{b:02x}").expect("Writing to String should never fail");
}
result
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonOutput {
pub matches: Vec<JsonMatchResult>,
}
impl JsonOutput {
#[must_use]
pub fn new(matches: Vec<JsonMatchResult>) -> Self {
Self { matches }
}
#[must_use]
pub fn from_evaluation_result(result: &EvaluationResult) -> Self {
let matches = result
.matches
.iter()
.map(JsonMatchResult::from_match_result)
.collect();
Self { matches }
}
pub fn add_match(&mut self, match_result: JsonMatchResult) {
self.matches.push(match_result);
}
#[must_use]
pub fn has_matches(&self) -> bool {
!self.matches.is_empty()
}
#[must_use]
pub fn match_count(&self) -> usize {
self.matches.len()
}
}
pub fn format_json_output(match_results: &[MatchResult]) -> Result<String, serde_json::Error> {
let json_matches: Vec<JsonMatchResult> = match_results
.iter()
.map(JsonMatchResult::from_match_result)
.collect();
let output = JsonOutput::new(json_matches);
serde_json::to_string_pretty(&output)
}
pub fn format_json_output_compact(
match_results: &[MatchResult],
) -> Result<String, serde_json::Error> {
let json_matches: Vec<JsonMatchResult> = match_results
.iter()
.map(JsonMatchResult::from_match_result)
.collect();
let output = JsonOutput::new(json_matches);
serde_json::to_string(&output)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonLineOutput {
pub filename: String,
pub matches: Vec<JsonMatchResult>,
}
impl JsonLineOutput {
#[must_use]
pub fn new(filename: String, matches: Vec<JsonMatchResult>) -> Self {
Self { filename, matches }
}
#[must_use]
pub fn from_match_results(filename: &Path, match_results: &[MatchResult]) -> Self {
let json_matches: Vec<JsonMatchResult> = match_results
.iter()
.map(JsonMatchResult::from_match_result)
.collect();
Self {
filename: filename.display().to_string(),
matches: json_matches,
}
}
}
pub fn format_json_line_output(
filename: &Path,
match_results: &[MatchResult],
) -> Result<String, serde_json::Error> {
let output = JsonLineOutput::from_match_results(filename, match_results);
serde_json::to_string(&output)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::output::{EvaluationMetadata, EvaluationResult, MatchResult};
use std::path::PathBuf;
#[test]
fn test_json_match_result_new() {
let result = JsonMatchResult::new(
"Test file".to_string(),
42,
"74657374".to_string(),
vec!["test".to_string()],
75,
);
assert_eq!(result.text, "Test file");
assert_eq!(result.offset, 42);
assert_eq!(result.value, "74657374");
assert_eq!(result.tags, vec!["test"]);
assert_eq!(result.score, 75);
}
#[test]
fn test_json_match_result_score_clamping() {
let result = JsonMatchResult::new(
"Test".to_string(),
0,
"00".to_string(),
vec![],
200, );
assert_eq!(result.score, 100);
}
#[test]
fn test_json_match_result_from_match_result() {
let match_result = MatchResult::with_metadata(
"ELF 64-bit LSB executable".to_string(),
0,
4,
Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
vec!["elf".to_string(), "elf64".to_string()],
95,
Some("application/x-executable".to_string()),
);
let json_result = JsonMatchResult::from_match_result(&match_result);
assert_eq!(json_result.text, "ELF 64-bit LSB executable");
assert_eq!(json_result.offset, 0);
assert_eq!(json_result.value, "7f454c46");
assert_eq!(json_result.tags, vec!["elf", "elf64"]);
assert_eq!(json_result.score, 95);
}
#[test]
fn test_json_match_result_add_tag() {
let mut result = JsonMatchResult::new(
"Archive".to_string(),
0,
"504b0304".to_string(),
vec!["archive".to_string()],
80,
);
result.add_tag("zip".to_string());
result.add_tag("compressed".to_string());
assert_eq!(result.tags, vec!["archive", "zip", "compressed"]);
}
#[test]
fn test_json_match_result_set_score() {
let mut result = JsonMatchResult::new("Test".to_string(), 0, "00".to_string(), vec![], 50);
result.set_score(85);
assert_eq!(result.score, 85);
result.set_score(150);
assert_eq!(result.score, 100);
result.set_score(0);
assert_eq!(result.score, 0);
}
#[test]
fn test_format_value_as_hex_bytes() {
let value = Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]);
assert_eq!(format_value_as_hex(&value), "7f454c46");
let empty_bytes = Value::Bytes(vec![]);
assert_eq!(format_value_as_hex(&empty_bytes), "");
let single_byte = Value::Bytes(vec![0xff]);
assert_eq!(format_value_as_hex(&single_byte), "ff");
}
#[test]
fn test_format_value_as_hex_string() {
let value = Value::String("PNG".to_string());
assert_eq!(format_value_as_hex(&value), "504e47");
let empty_string = Value::String(String::new());
assert_eq!(format_value_as_hex(&empty_string), "");
let unicode_string = Value::String("🦀".to_string());
assert_eq!(format_value_as_hex(&unicode_string), "f09fa680");
}
#[test]
fn test_format_value_as_hex_uint() {
let value = Value::Uint(0x1234);
assert_eq!(format_value_as_hex(&value), "3412000000000000");
let zero = Value::Uint(0);
assert_eq!(format_value_as_hex(&zero), "0000000000000000");
let max_value = Value::Uint(u64::MAX);
assert_eq!(format_value_as_hex(&max_value), "ffffffffffffffff");
}
#[test]
fn test_format_value_as_hex_int() {
let positive = Value::Int(0x1234);
assert_eq!(format_value_as_hex(&positive), "3412000000000000");
let negative = Value::Int(-1);
assert_eq!(format_value_as_hex(&negative), "ffffffffffffffff");
let zero = Value::Int(0);
assert_eq!(format_value_as_hex(&zero), "0000000000000000");
}
#[test]
fn test_json_output_new() {
let matches = vec![
JsonMatchResult::new(
"Match 1".to_string(),
0,
"01".to_string(),
vec!["tag1".to_string()],
60,
),
JsonMatchResult::new(
"Match 2".to_string(),
10,
"02".to_string(),
vec!["tag2".to_string()],
70,
),
];
let output = JsonOutput::new(matches);
assert_eq!(output.matches.len(), 2);
assert_eq!(output.matches[0].text, "Match 1");
assert_eq!(output.matches[1].text, "Match 2");
}
#[test]
fn test_json_output_from_evaluation_result() {
let match_results = vec![
MatchResult::with_metadata(
"PNG image".to_string(),
0,
8,
Value::Bytes(vec![0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
vec!["image".to_string(), "png".to_string()],
90,
Some("image/png".to_string()),
),
MatchResult::with_metadata(
"8-bit color".to_string(),
25,
1,
Value::Uint(8),
vec!["image".to_string(), "png".to_string(), "color".to_string()],
75,
None,
),
];
let metadata = EvaluationMetadata::new(2048, 3.2, 15, 2);
let eval_result = EvaluationResult::new(PathBuf::from("test.png"), match_results, metadata);
let json_output = JsonOutput::from_evaluation_result(&eval_result);
assert_eq!(json_output.matches.len(), 2);
assert_eq!(json_output.matches[0].text, "PNG image");
assert_eq!(json_output.matches[0].value, "89504e470d0a1a0a");
assert_eq!(json_output.matches[0].tags, vec!["image", "png"]);
assert_eq!(json_output.matches[0].score, 90);
assert_eq!(json_output.matches[1].text, "8-bit color");
assert_eq!(json_output.matches[1].value, "0800000000000000");
assert_eq!(json_output.matches[1].tags, vec!["image", "png", "color"]);
assert_eq!(json_output.matches[1].score, 75);
}
#[test]
fn test_json_output_add_match() {
let mut output = JsonOutput::new(vec![]);
let match_result = JsonMatchResult::new(
"PDF document".to_string(),
0,
"25504446".to_string(),
vec!["document".to_string(), "pdf".to_string()],
85,
);
output.add_match(match_result);
assert_eq!(output.matches.len(), 1);
assert_eq!(output.matches[0].text, "PDF document");
}
#[test]
fn test_json_output_has_matches() {
let empty_output = JsonOutput::new(vec![]);
assert!(!empty_output.has_matches());
let output_with_matches = JsonOutput::new(vec![JsonMatchResult::new(
"Test".to_string(),
0,
"74657374".to_string(),
vec![],
50,
)]);
assert!(output_with_matches.has_matches());
}
#[test]
fn test_json_output_match_count() {
let empty_output = JsonOutput::new(vec![]);
assert_eq!(empty_output.match_count(), 0);
let matches = vec![
JsonMatchResult::new("Match 1".to_string(), 0, "01".to_string(), vec![], 50),
JsonMatchResult::new("Match 2".to_string(), 10, "02".to_string(), vec![], 60),
JsonMatchResult::new("Match 3".to_string(), 20, "03".to_string(), vec![], 70),
];
let output = JsonOutput::new(matches);
assert_eq!(output.match_count(), 3);
}
#[test]
fn test_json_match_result_serialization() {
let result = JsonMatchResult::new(
"JPEG image".to_string(),
0,
"ffd8".to_string(),
vec!["image".to_string(), "jpeg".to_string()],
80,
);
let json = serde_json::to_string(&result).expect("Failed to serialize JsonMatchResult");
let deserialized: JsonMatchResult =
serde_json::from_str(&json).expect("Failed to deserialize JsonMatchResult");
assert_eq!(result, deserialized);
}
#[test]
fn test_json_output_serialization() {
let matches = vec![
JsonMatchResult::new(
"ELF executable".to_string(),
0,
"7f454c46".to_string(),
vec!["executable".to_string(), "elf".to_string()],
95,
),
JsonMatchResult::new(
"64-bit".to_string(),
4,
"02".to_string(),
vec!["elf".to_string(), "64bit".to_string()],
85,
),
];
let output = JsonOutput::new(matches);
let json = serde_json::to_string(&output).expect("Failed to serialize JsonOutput");
let deserialized: JsonOutput =
serde_json::from_str(&json).expect("Failed to deserialize JsonOutput");
assert_eq!(output.matches.len(), deserialized.matches.len());
assert_eq!(output.matches[0].text, deserialized.matches[0].text);
assert_eq!(output.matches[1].text, deserialized.matches[1].text);
}
#[test]
fn test_json_output_serialization_format() {
let matches = vec![JsonMatchResult::new(
"Test file".to_string(),
0,
"74657374".to_string(),
vec!["test".to_string()],
75,
)];
let output = JsonOutput::new(matches);
let json = serde_json::to_string_pretty(&output).expect("Failed to serialize");
assert!(json.contains("\"matches\""));
assert!(json.contains("\"text\": \"Test file\""));
assert!(json.contains("\"offset\": 0"));
assert!(json.contains("\"value\": \"74657374\""));
assert!(json.contains("\"tags\""));
assert!(json.contains("\"test\""));
assert!(json.contains("\"score\": 75"));
}
#[test]
fn test_json_match_result_equality() {
let result1 = JsonMatchResult::new(
"Test".to_string(),
0,
"74657374".to_string(),
vec!["test".to_string()],
50,
);
let result2 = JsonMatchResult::new(
"Test".to_string(),
0,
"74657374".to_string(),
vec!["test".to_string()],
50,
);
let result3 = JsonMatchResult::new(
"Different".to_string(),
0,
"74657374".to_string(),
vec!["test".to_string()],
50,
);
assert_eq!(result1, result2);
assert_ne!(result1, result3);
}
#[test]
fn test_complex_json_conversion() {
let match_result = MatchResult::with_metadata(
"ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked"
.to_string(),
0,
4,
Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
vec![
"executable".to_string(),
"elf".to_string(),
"elf64".to_string(),
"x86_64".to_string(),
"pie".to_string(),
"dynamic".to_string(),
],
98,
Some("application/x-pie-executable".to_string()),
);
let json_result = JsonMatchResult::from_match_result(&match_result);
assert_eq!(
json_result.text,
"ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked"
);
assert_eq!(json_result.offset, 0);
assert_eq!(json_result.value, "7f454c46");
assert_eq!(
json_result.tags,
vec!["executable", "elf", "elf64", "x86_64", "pie", "dynamic"]
);
assert_eq!(json_result.score, 98);
}
#[test]
fn test_format_json_output_single_match() {
let match_results = vec![MatchResult::with_metadata(
"PNG image".to_string(),
0,
8,
Value::Bytes(vec![0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
vec!["image".to_string(), "png".to_string()],
90,
Some("image/png".to_string()),
)];
let json_output = format_json_output(&match_results).expect("Failed to format JSON");
assert!(json_output.contains("\"matches\""));
assert!(json_output.contains("\"text\": \"PNG image\""));
assert!(json_output.contains("\"offset\": 0"));
assert!(json_output.contains("\"value\": \"89504e470d0a1a0a\""));
assert!(json_output.contains("\"tags\""));
assert!(json_output.contains("\"image\""));
assert!(json_output.contains("\"png\""));
assert!(json_output.contains("\"score\": 90"));
let parsed: JsonOutput =
serde_json::from_str(&json_output).expect("Generated JSON should be valid");
assert_eq!(parsed.matches.len(), 1);
assert_eq!(parsed.matches[0].text, "PNG image");
assert_eq!(parsed.matches[0].offset, 0);
assert_eq!(parsed.matches[0].value, "89504e470d0a1a0a");
assert_eq!(parsed.matches[0].tags, vec!["image", "png"]);
assert_eq!(parsed.matches[0].score, 90);
}
#[test]
fn test_format_json_output_multiple_matches() {
let match_results = vec![
MatchResult::with_metadata(
"ELF 64-bit LSB executable".to_string(),
0,
4,
Value::Bytes(vec![0x7f, 0x45, 0x4c, 0x46]),
vec!["executable".to_string(), "elf".to_string()],
95,
Some("application/x-executable".to_string()),
),
MatchResult::with_metadata(
"x86-64 architecture".to_string(),
18,
2,
Value::Uint(0x3e00),
vec!["elf".to_string(), "x86_64".to_string()],
85,
None,
),
MatchResult::with_metadata(
"dynamically linked".to_string(),
16,
2,
Value::Uint(0x0200),
vec!["elf".to_string(), "dynamic".to_string()],
80,
None,
),
];
let json_output = format_json_output(&match_results).expect("Failed to format JSON");
assert!(json_output.contains("\"text\": \"ELF 64-bit LSB executable\""));
assert!(json_output.contains("\"text\": \"x86-64 architecture\""));
assert!(json_output.contains("\"text\": \"dynamically linked\""));
assert!(json_output.contains("\"offset\": 0"));
assert!(json_output.contains("\"offset\": 18"));
assert!(json_output.contains("\"offset\": 16"));
assert!(json_output.contains("\"value\": \"7f454c46\""));
assert!(json_output.contains("\"value\": \"003e000000000000\""));
assert!(json_output.contains("\"value\": \"0002000000000000\""));
let parsed: JsonOutput =
serde_json::from_str(&json_output).expect("Generated JSON should be valid");
assert_eq!(parsed.matches.len(), 3);
assert_eq!(parsed.matches[0].text, "ELF 64-bit LSB executable");
assert_eq!(parsed.matches[0].offset, 0);
assert_eq!(parsed.matches[0].score, 95);
assert_eq!(parsed.matches[1].text, "x86-64 architecture");
assert_eq!(parsed.matches[1].offset, 18);
assert_eq!(parsed.matches[1].score, 85);
assert_eq!(parsed.matches[2].text, "dynamically linked");
assert_eq!(parsed.matches[2].offset, 16);
assert_eq!(parsed.matches[2].score, 80);
}
#[test]
fn test_format_json_output_empty_matches() {
let match_results: Vec<MatchResult> = vec![];
let json_output = format_json_output(&match_results).expect("Failed to format JSON");
assert!(json_output.contains("\"matches\": []"));
let parsed: JsonOutput =
serde_json::from_str(&json_output).expect("Generated JSON should be valid");
assert_eq!(parsed.matches.len(), 0);
assert!(!parsed.has_matches());
}
#[test]
fn test_format_json_output_compact_single_match() {
let match_results = vec![MatchResult::new(
"JPEG image".to_string(),
0,
Value::Bytes(vec![0xff, 0xd8]),
)];
let json_output =
format_json_output_compact(&match_results).expect("Failed to format compact JSON");
assert!(!json_output.contains('\n'));
assert!(!json_output.contains(" "));
assert!(json_output.contains("\"matches\""));
assert!(json_output.contains("\"text\":\"JPEG image\""));
assert!(json_output.contains("\"offset\":0"));
assert!(json_output.contains("\"value\":\"ffd8\""));
let parsed: JsonOutput =
serde_json::from_str(&json_output).expect("Generated JSON should be valid");
assert_eq!(parsed.matches.len(), 1);
assert_eq!(parsed.matches[0].text, "JPEG image");
}
#[test]
fn test_format_json_output_compact_multiple_matches() {
let match_results = vec![
MatchResult::new("Match 1".to_string(), 0, Value::String("test1".to_string())),
MatchResult::new(
"Match 2".to_string(),
10,
Value::String("test2".to_string()),
),
];
let json_output =
format_json_output_compact(&match_results).expect("Failed to format compact JSON");
assert!(!json_output.contains('\n'));
assert!(json_output.contains("\"text\":\"Match 1\""));
assert!(json_output.contains("\"text\":\"Match 2\""));
let parsed: JsonOutput =
serde_json::from_str(&json_output).expect("Generated JSON should be valid");
assert_eq!(parsed.matches.len(), 2);
}
#[test]
fn test_format_json_output_compact_empty() {
let match_results: Vec<MatchResult> = vec![];
let json_output =
format_json_output_compact(&match_results).expect("Failed to format compact JSON");
assert!(!json_output.contains('\n'));
assert!(json_output.contains("\"matches\":[]"));
let parsed: JsonOutput =
serde_json::from_str(&json_output).expect("Generated JSON should be valid");
assert_eq!(parsed.matches.len(), 0);
}
#[test]
fn test_format_json_output_field_mapping() {
let match_result = MatchResult::with_metadata(
"Test file with all fields".to_string(),
42,
8,
Value::Bytes(vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]),
vec![
"category".to_string(),
"subcategory".to_string(),
"specific".to_string(),
],
75,
Some("application/test".to_string()),
);
let json_output = format_json_output(&[match_result]).expect("Failed to format JSON");
assert!(json_output.contains("\"text\": \"Test file with all fields\""));
assert!(json_output.contains("\"offset\": 42"));
assert!(json_output.contains("\"value\": \"0102030405060708\""));
assert!(json_output.contains("\"tags\""));
assert!(json_output.contains("\"category\""));
assert!(json_output.contains("\"subcategory\""));
assert!(json_output.contains("\"specific\""));
assert!(json_output.contains("\"score\": 75"));
let parsed: JsonOutput =
serde_json::from_str(&json_output).expect("Generated JSON should be valid");
assert_eq!(parsed.matches.len(), 1);
let json_match = &parsed.matches[0];
assert_eq!(json_match.text, "Test file with all fields");
assert_eq!(json_match.offset, 42);
assert_eq!(json_match.value, "0102030405060708");
assert_eq!(json_match.tags, vec!["category", "subcategory", "specific"]);
assert_eq!(json_match.score, 75);
}
#[test]
fn test_format_json_output_different_value_types() {
let match_results = vec![
MatchResult::new(
"Bytes value".to_string(),
0,
Value::Bytes(vec![0xde, 0xad, 0xbe, 0xef]),
),
MatchResult::new(
"String value".to_string(),
10,
Value::String("Hello, World!".to_string()),
),
MatchResult::new("Uint value".to_string(), 20, Value::Uint(0x1234_5678)),
MatchResult::new("Int value".to_string(), 30, Value::Int(-42)),
];
let json_output = format_json_output(&match_results).expect("Failed to format JSON");
assert!(json_output.contains("\"value\": \"deadbeef\""));
assert!(json_output.contains("\"value\": \"48656c6c6f2c20576f726c6421\""));
assert!(json_output.contains("\"value\": \"7856341200000000\""));
assert!(json_output.contains("\"value\": \"d6ffffffffffffff\""));
let parsed: JsonOutput =
serde_json::from_str(&json_output).expect("Generated JSON should be valid");
assert_eq!(parsed.matches.len(), 4);
}
#[test]
fn test_format_json_output_validation() {
let match_result = MatchResult::with_metadata(
"PDF document".to_string(),
0,
4,
Value::String("%PDF".to_string()),
vec!["document".to_string(), "pdf".to_string()],
88,
Some("application/pdf".to_string()),
);
let json_output = format_json_output(&[match_result]).expect("Failed to format JSON");
let parsed: serde_json::Value =
serde_json::from_str(&json_output).expect("Generated JSON should be valid");
assert!(parsed.is_object());
assert!(parsed.get("matches").is_some());
assert!(parsed.get("matches").unwrap().is_array());
let matches = parsed.get("matches").unwrap().as_array().unwrap();
assert_eq!(matches.len(), 1);
let match_obj = &matches[0];
assert!(match_obj.get("text").is_some());
assert!(match_obj.get("offset").is_some());
assert!(match_obj.get("value").is_some());
assert!(match_obj.get("tags").is_some());
assert!(match_obj.get("score").is_some());
assert!(match_obj.get("text").unwrap().is_string());
assert!(match_obj.get("offset").unwrap().is_number());
assert!(match_obj.get("value").unwrap().is_string());
assert!(match_obj.get("tags").unwrap().is_array());
assert!(match_obj.get("score").unwrap().is_number());
assert_eq!(
match_obj.get("text").unwrap().as_str().unwrap(),
"PDF document"
);
assert_eq!(match_obj.get("offset").unwrap().as_u64().unwrap(), 0);
assert_eq!(
match_obj.get("value").unwrap().as_str().unwrap(),
"25504446"
);
assert_eq!(match_obj.get("score").unwrap().as_u64().unwrap(), 88);
let tags = match_obj.get("tags").unwrap().as_array().unwrap();
assert_eq!(tags.len(), 2);
assert_eq!(tags[0].as_str().unwrap(), "document");
assert_eq!(tags[1].as_str().unwrap(), "pdf");
}
}