1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use clap::Parser;
use std::path::PathBuf;
// Import from library crate for error type
use rjd::RjdError;
/// Output format options
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum OutputFormat {
#[value(name = "changes")]
Changes, // Default: {added, removed, modified}
#[value(name = "after")]
After, // Output the "after" state with only changed properties
#[value(name = "rfc6902")]
Rfc6902, // RFC 6902 compliant JSON Patch format
}
impl std::fmt::Display for OutputFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OutputFormat::Changes => write!(f, "changes"),
OutputFormat::After => write!(f, "after"),
OutputFormat::Rfc6902 => write!(f, "rfc6902"),
}
}
}
/// Command-line arguments for rjd
#[derive(Parser, Debug)]
#[command(name = "rjd")]
#[command(about = "Compare two JSON files or inline JSON strings")]
pub struct Args {
/// First JSON file or inline JSON string
pub file1: String,
/// Second JSON file or inline JSON string (not required when using --stdin)
#[arg(required = false)]
pub file2: Option<String>,
/// Read the second JSON input from stdin
#[arg(long)]
pub stdin: bool,
/// Output format (default: changes)
#[arg(short, long, default_value_t = OutputFormat::Changes, hide_default_value = true)]
pub format: OutputFormat,
/// Sort keys in output
#[arg(long)]
pub sort: bool,
/// JSON file containing paths to ignore (can be specified multiple times)
#[arg(long)]
pub ignore_json: Vec<String>,
/// Maximum file size in bytes (default: 104857600, env: RJD_MAX_FILE_SIZE)
#[arg(long)]
pub max_file_size: Option<u64>,
/// Maximum JSON nesting depth (default: 1000, env: RJD_MAX_JSON_DEPTH)
#[arg(long)]
pub max_depth: Option<usize>,
/// Follow symbolic links (default: false, env: RJD_FOLLOW_SYMLINKS)
#[arg(long)]
pub follow_symlinks: bool,
/// Force input to be treated as inline JSON
#[arg(long)]
pub inline: bool,
}
impl Args {
/// Validate command-line arguments
pub fn validate(&self) -> Result<(), RjdError> {
// If not using stdin, file2 must be provided
if !self.stdin && self.file2.is_none() {
return Err(RjdError::MissingFile2);
}
// Validate ignore files exist
for ignore_path in &self.ignore_json {
let path = PathBuf::from(ignore_path);
if !path.exists() {
return Err(RjdError::FileRead {
path,
source: std::io::Error::new(
std::io::ErrorKind::NotFound,
"Ignore file not found",
),
});
}
}
Ok(())
}
}