dev_kit/command/json/
json.rs1use super::{DiffTool, Json};
2use crate::command::http_parser::HttpRequest;
3use crate::command::read_stdin;
4use anyhow::anyhow;
5use itertools::Itertools;
6use jsonpath_rust::JsonPath;
7use lazy_static::lazy_static;
8use std::path::PathBuf;
9use std::process::Command;
10use std::str::FromStr;
11use std::sync::Arc;
12use std::{env, fs};
13
14impl Json {
15 pub fn keys(&self, query: Option<&str>) -> crate::Result<Vec<String>> {
16 let json = Arc::<serde_json::Value>::try_from(self)?;
17 let result = if let Some(query) = query {
18 json.query(query)?
19 } else {
20 vec![&*json]
21 };
22 let keys = result.iter().flat_map(|it| match it {
23 serde_json::Value::Object(map) => map.keys().map(|k| k.to_string()).collect_vec(),
24 serde_json::Value::Array(_) => vec!["*".to_string()],
25 _ => vec![]
26 }).unique_by(|it| it.clone()).collect_vec();
27 Ok(keys)
28 }
29 pub fn beautify(&self, query: Option<&str>) -> crate::Result<String> {
30 let json = Arc::<serde_json::Value>::try_from(self)?;
31 let result = if let Some(query) = query {
32 let json = json.query(query)?;
33 serde_json::to_string_pretty(&json)
34 } else {
35 serde_json::to_string_pretty(&*json)
36 };
37 Ok(result.map_err(|err| {
38 log::debug!("{}",err);
39 anyhow!("Invalid json format")
40 })?)
41 }
42
43 pub fn query(&self, query: &str, beauty: bool) -> crate::Result<Vec<String>> {
44 let json = Arc::<serde_json::Value>::try_from(self)?;
45 let query_result = json.query(query).map_err(|err| {
46 log::debug!("{}",err);
47 anyhow!("Invalid json path: {query}")
48 })?;
49 let arr = query_result.iter().flat_map(|&it| {
50 if beauty {
51 serde_json::to_string_pretty(&it)
52 } else {
53 serde_json::to_string(&it)
54 }.map_err(|err| {
55 log::debug!("{}",err);
56 anyhow!("Invalid json format")
57 })
58 }).collect_vec();
59 Ok(arr)
60 }
61
62 pub fn diff(&self, other: &Self, query: Option<&str>, diff_tool: Option<DiffTool>) -> crate::Result<()> {
63 let tmp_dir = env::temp_dir().join("jsondiff").join(chrono::Local::now().format("%Y%m%d%H%M%S%f").to_string());
64 if tmp_dir.exists() {
65 fs::remove_dir_all(&tmp_dir)?;
66 }
67 let left = self;
68 let right = other;
69 let _ = fs::create_dir_all(&tmp_dir)?;
70 let left = left.diff_prepare(query.as_deref())?;
71 let left_path = tmp_dir.join("left.json");
72 fs::write(&left_path, left)?;
73 println!("write left to file {}", left_path.display());
74 let right = right.diff_prepare(query.as_deref())?;
75 let right_path = tmp_dir.join("right.json");
76 fs::write(&right_path, right)?;
77 println!("write right to file {}", right_path.display());
78 let diff_tool = diff_tool.unwrap_or_default();
79 if diff_tool.is_available() {
80 println!("diff with {}", diff_tool);
81 diff_tool.diff(&left_path, &right_path)?;
82 } else {
83 eprintln!("diff tool {} is not installed", diff_tool);
84 println!(
85 r#"
86install {} command-line interface, see:
87{}"#,
88 diff_tool, diff_tool.how_to_install()
89 )
90 }
91 Ok(())
92 }
93}
94
95impl Json {
96 fn diff_prepare(&self, query: Option<&str>) -> crate::Result<String> {
97 let json = Arc::<serde_json::Value>::try_from(self)?;
98 if let Some(query) = query {
99 let array = json.query(query)?;
100 let pretty = serde_json::to_string_pretty(&array)?;
101 Ok(pretty)
102 } else {
103 Ok(serde_json::to_string_pretty(&*json)?)
104 }
105 }
106}
107
108lazy_static! {
109 static ref ASYNC_RT: tokio::runtime::Runtime = {
110 tokio::runtime::Builder::new_multi_thread()
111 .worker_threads(1usize)
112 .enable_all()
113 .build().unwrap()
114 };
115}
116
117impl TryFrom<&Json> for Arc<serde_json::Value> {
118 type Error = anyhow::Error;
119
120 fn try_from(input: &Json) -> Result<Self, Self::Error> {
121 let json = match input {
122 Json::Cmd(input) | Json::String(input) => {
123 let json = serde_json::from_str::<serde_json::Value>(&input).map_err(|err| {
124 log::debug!("{}",err);
125 anyhow!("Invalid json format")
126 })?;
127 Arc::new(json)
128 }
129 Json::Path(path) => {
130 let file = fs::File::open(&path).map_err(|err| anyhow!("open file {} failed, {}", path.display(), err))?;
131 let json = serde_json::from_reader::<_, serde_json::Value>(file).map_err(|err| {
132 log::debug!("{}",err);
133 anyhow!("Invalid json format")
134 })?;
135 Arc::new(json)
136 }
137 Json::HttpRequest(http_request) => {
138 Arc::new(http_request.try_into()?)
139 }
140 Json::JsonValue(val) => Arc::clone(val)
141 };
142 Ok(json)
143 }
144}
145
146lazy_static! {
147 static ref CMD_SPLIT_PATTERN: regex::Regex = {
148 regex::RegexBuilder::new(r"^([\w\d]+).*")
149 .multi_line(true)
150 .case_insensitive(true)
151 .build().unwrap()
152 };
153}
154impl FromStr for Json {
155 type Err = anyhow::Error;
156
157 fn from_str(value: &str) -> Result<Self, Self::Err> {
158 let result = if let Some(string) = read_stdin() {
159 Ok(Self::from_str(&string)?)
160 } else if let Ok(http_request) = HttpRequest::from_str(value) {
161 Ok(Json::HttpRequest(http_request))
162 } else if let Some(_cmd_path) = CMD_SPLIT_PATTERN.captures(&value)
163 .map(|c| c.extract())
164 .and_then(|(_, [cmd])| which::which(cmd).ok()) {
165 Ok(Json::Cmd(run_cmd(value)?))
166 } else if let Ok(path) = {
167 let path = PathBuf::from_str(value)?;
168 if fs::exists(&path).unwrap_or(false) {
169 Ok(path)
170 } else {
171 Err(anyhow!("Not a valid path: {}", value))
172 }
173 } {
174 Ok(Json::Path(path))
175 } else {
176 Ok(Json::String(value.to_string()))
177 };
178 match result {
179 Ok(json) => {
180 log::debug!("guess str to Json ok, {} => str: {value}", json.name());
181 Ok(json)
182 }
183 Err(err) => {
184 log::debug!("guess str to Json failed, str: {value}, err: {err}");
185 Err(err)
186 }
187 }
188 }
189}
190
191impl TryFrom<&String> for Json {
192 type Error = anyhow::Error;
193
194 fn try_from(value: &String) -> Result<Self, Self::Error> {
195 Self::from_str(value)
196 }
197}
198
199fn run_cmd(value: &str) -> crate::Result<String> {
200 let output = Command::new("sh")
201 .arg("-c")
202 .arg(value)
203 .output()
204 .map_err(|err| anyhow!(r#"
205failed to execute command: {}
206{}
207"#, err, value
208 ))?;
209 if output.status.success() {
210 let stdout = String::from_utf8(output.stdout).map_err(|err|
211 anyhow!("failed to parse output as UTF-8: {}", err)
212 )?;
213 Ok(stdout)
214 } else {
215 let stderr = String::from_utf8_lossy(&output.stderr);
216 Err(anyhow!("run command failed: {}", stderr))
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use std::io::Write;
224 use tempfile::NamedTempFile;
225 use url::Url;
226
227 #[test]
228 fn test_json_from_str_string() {
229 let input = r#"{"a": 1}"#;
230 let json = Json::from_str(input).unwrap();
231 match json {
232 Json::String(s) => assert_eq!(s, input),
233 _ => panic!("Expected Json::String, got {:?}", json),
234 }
235 }
236
237 #[test]
238 fn test_json_from_str_path() {
239 let mut file = NamedTempFile::new().unwrap();
240 let content = r#"{"a": 1}"#;
241 writeln!(file, "{}", content).unwrap();
242 let path_str = file.path().to_str().unwrap();
243
244 let json = Json::from_str(path_str).unwrap();
245 match json {
246 Json::Path(p) => assert_eq!(p, file.path()),
247 _ => panic!("Expected Json::Path, got {:?}", json),
248 }
249 }
250
251 #[test]
252 fn test_json_from_str_url_http() {
253 let input = "http://example.com/api.json";
254 let json = Json::from_str(input).unwrap();
255 match json {
256 Json::HttpRequest(http_request) => {
257 let url = Url::try_from(&http_request).unwrap();
258 assert_eq!(url.as_str(), input)
259 }
260 _ => panic!("Expected Json::Uri, got {:?}", json),
261 }
262 }
263
264 #[test]
265 fn test_json_from_str_cmd() {
266 let input = "echo '{\"a\": 1}'";
268 let json = Json::from_str(input).unwrap();
269 match json {
270 Json::Cmd(s) => assert!(s.contains("\"a\": 1")),
271 _ => panic!("Expected Json::Cmd, got {:?}", json),
272 }
273 }
274
275 #[test]
276 fn test_json_beautify() {
277 let input = r#"{"a":1,"b":2}"#;
278 let json = Json::String(input.to_string());
279 let beautified = json.beautify(None).unwrap();
280 assert!(beautified.contains("\n \"a\": 1,"));
281 assert!(beautified.contains("\n \"b\": 2"));
282 }
283
284 #[test]
285 fn test_json_query() {
286 let input = r#"{"a":{"b":1},"c":2}"#;
287 let json = Json::String(input.to_string());
288 let result = json.query("$.a.b", false).unwrap();
289 assert_eq!(result, vec!["1"]);
290
291 let result = json.query("$.a", false).unwrap();
292 assert_eq!(result, vec![r#"{"b":1}"#]);
293 }
294
295 #[test]
296 fn test_json_diff_prepare() {
297 let input = r#"{"a":1,"b":2}"#;
298 let json = Json::String(input.to_string());
299
300 let prepared = json.diff_prepare(None).unwrap();
302 assert!(prepared.contains("\"a\": 1"));
303
304 let prepared = json.diff_prepare(Some("$.a")).unwrap();
306 assert_eq!(prepared, "[\n 1\n]");
307 }
308
309 #[test]
310 fn test_run_cmd_success() {
311 let result = run_cmd("echo 'hello'").unwrap();
312 assert_eq!(result.trim(), "hello");
313 }
314
315 #[test]
316 fn test_try_from_json_for_value() {
317 let json_str = Json::String(r#"{"a": 1}"#.to_string());
319 let value = Arc::<serde_json::Value>::try_from(&json_str).unwrap();
320 assert_eq!(value["a"], 1);
321
322 let mut file = NamedTempFile::new().unwrap();
324 writeln!(file, r#"{{"b": 2}}"#).unwrap();
325 let json_path = Json::Path(file.path().to_path_buf());
326 let value = Arc::<serde_json::Value>::try_from(&json_path).unwrap();
327 assert_eq!(value["b"], 2);
328
329 let json_cmd = Json::Cmd(r#"{"c": 3}"#.to_string());
331 let value = Arc::<serde_json::Value>::try_from(&json_cmd).unwrap();
332 assert_eq!(value["c"], 3);
333 }
334
335 #[test]
336 fn test_json_from_str_invalid() {
337 let input = "invalid json";
339 let json = Json::from_str(input).unwrap();
340 match json {
341 Json::String(s) => assert_eq!(s, input),
342 _ => panic!("Expected Json::String"),
343 }
344
345 let json_obj = Json::String(input.to_string());
347 let result = Arc::<serde_json::Value>::try_from(&json_obj);
348 assert!(result.is_err());
349 }
350
351 #[test]
352 fn test_run_cmd_error_output() {
353 let result = run_cmd("ls /non_existent_directory_12345");
355 assert!(result.is_err());
356 assert!(result.unwrap_err().to_string().contains("run command failed"));
357 }
358}