dev_kit/command/json/
json.rs1use super::{DiffTool, Json};
2use crate::command::http_parser::HttpRequest;
3use anyhow::anyhow;
4use itertools::Itertools;
5use jsonpath_rust::JsonPath;
6use lazy_static::lazy_static;
7use std::io::Read;
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 value.eq("-") {
159 let mut string = String::new();
160 let _ = std::io::stdin().lock().read_to_string(&mut string)
161 .map_err(|err| anyhow!("read from stdin failed, {}", err))?;
162 match string.trim() {
163 "-" => Err(anyhow!("Not a valid input")),
164 _ => Ok(Self::from_str(&string)?)
165 }
166 } else if let Ok(http_request) = HttpRequest::from_str(value) {
167 Ok(Json::HttpRequest(http_request))
168 } else if let Some(_cmd_path) = CMD_SPLIT_PATTERN.captures(&value)
169 .map(|c| c.extract())
170 .and_then(|(_, [cmd])| which::which(cmd).ok()) {
171 Ok(Json::Cmd(run_cmd(value)?))
172 } else if let Ok(path) = {
173 let path = PathBuf::from_str(value)?;
174 if fs::exists(&path).unwrap_or(false) {
175 Ok(path)
176 } else {
177 Err(anyhow!("Not a valid path: {}", value))
178 }
179 } {
180 Ok(Json::Path(path))
181 } else {
182 Ok(Json::String(value.to_string()))
183 };
184 match result {
185 Ok(json) => {
186 log::debug!("guess str to Json ok, {} => str: {value}", json.name());
187 Ok(json)
188 }
189 Err(err) => {
190 log::debug!("guess str to Json failed, str: {value}, err: {err}");
191 Err(err)
192 }
193 }
194 }
195}
196
197impl TryFrom<&String> for Json {
198 type Error = anyhow::Error;
199
200 fn try_from(value: &String) -> Result<Self, Self::Error> {
201 Self::from_str(value)
202 }
203}
204
205fn run_cmd(value: &str) -> crate::Result<String> {
206 let output = Command::new("sh")
207 .arg("-c")
208 .arg(value)
209 .output()
210 .map_err(|err| anyhow!(r#"
211failed to execute command: {}
212{}
213"#, err, value
214 ))?;
215 if output.status.success() {
216 let stdout = String::from_utf8(output.stdout).map_err(|err|
217 anyhow!("failed to parse output as UTF-8: {}", err)
218 )?;
219 Ok(stdout)
220 } else {
221 let stderr = String::from_utf8_lossy(&output.stderr);
222 Err(anyhow!("run command failed: {}", stderr))
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use std::io::Write;
230 use tempfile::NamedTempFile;
231 use url::Url;
232
233 #[test]
234 fn test_json_from_str_string() {
235 let input = r#"{"a": 1}"#;
236 let json = Json::from_str(input).unwrap();
237 match json {
238 Json::String(s) => assert_eq!(s, input),
239 _ => panic!("Expected Json::String, got {:?}", json),
240 }
241 }
242
243 #[test]
244 fn test_json_from_str_path() {
245 let mut file = NamedTempFile::new().unwrap();
246 let content = r#"{"a": 1}"#;
247 writeln!(file, "{}", content).unwrap();
248 let path_str = file.path().to_str().unwrap();
249
250 let json = Json::from_str(path_str).unwrap();
251 match json {
252 Json::Path(p) => assert_eq!(p, file.path()),
253 _ => panic!("Expected Json::Path, got {:?}", json),
254 }
255 }
256
257 #[test]
258 fn test_json_from_str_url_http() {
259 let input = "http://example.com/api.json";
260 let json = Json::from_str(input).unwrap();
261 match json {
262 Json::HttpRequest(http_request) => {
263 let url = Url::try_from(&http_request).unwrap();
264 assert_eq!(url.as_str(), input)
265 }
266 _ => panic!("Expected Json::Uri, got {:?}", json),
267 }
268 }
269
270 #[test]
271 fn test_json_from_str_cmd() {
272 let input = "echo '{\"a\": 1}'";
274 let json = Json::from_str(input).unwrap();
275 match json {
276 Json::Cmd(s) => assert!(s.contains("\"a\": 1")),
277 _ => panic!("Expected Json::Cmd, got {:?}", json),
278 }
279 }
280
281 #[test]
282 fn test_json_beautify() {
283 let input = r#"{"a":1,"b":2}"#;
284 let json = Json::String(input.to_string());
285 let beautified = json.beautify(None).unwrap();
286 assert!(beautified.contains("\n \"a\": 1,"));
287 assert!(beautified.contains("\n \"b\": 2"));
288 }
289
290 #[test]
291 fn test_json_query() {
292 let input = r#"{"a":{"b":1},"c":2}"#;
293 let json = Json::String(input.to_string());
294 let result = json.query("$.a.b", false).unwrap();
295 assert_eq!(result, vec!["1"]);
296
297 let result = json.query("$.a", false).unwrap();
298 assert_eq!(result, vec![r#"{"b":1}"#]);
299 }
300
301 #[test]
302 fn test_json_diff_prepare() {
303 let input = r#"{"a":1,"b":2}"#;
304 let json = Json::String(input.to_string());
305
306 let prepared = json.diff_prepare(None).unwrap();
308 assert!(prepared.contains("\"a\": 1"));
309
310 let prepared = json.diff_prepare(Some("$.a")).unwrap();
312 assert_eq!(prepared, "[\n 1\n]");
313 }
314
315 #[test]
316 fn test_run_cmd_success() {
317 let result = run_cmd("echo 'hello'").unwrap();
318 assert_eq!(result.trim(), "hello");
319 }
320
321 #[test]
322 fn test_try_from_json_for_value() {
323 let json_str = Json::String(r#"{"a": 1}"#.to_string());
325 let value = Arc::<serde_json::Value>::try_from(&json_str).unwrap();
326 assert_eq!(value["a"], 1);
327
328 let mut file = NamedTempFile::new().unwrap();
330 writeln!(file, r#"{{"b": 2}}"#).unwrap();
331 let json_path = Json::Path(file.path().to_path_buf());
332 let value = Arc::<serde_json::Value>::try_from(&json_path).unwrap();
333 assert_eq!(value["b"], 2);
334
335 let json_cmd = Json::Cmd(r#"{"c": 3}"#.to_string());
337 let value = Arc::<serde_json::Value>::try_from(&json_cmd).unwrap();
338 assert_eq!(value["c"], 3);
339 }
340
341 #[test]
342 fn test_json_from_str_invalid() {
343 let input = "invalid json";
345 let json = Json::from_str(input).unwrap();
346 match json {
347 Json::String(s) => assert_eq!(s, input),
348 _ => panic!("Expected Json::String"),
349 }
350
351 let json_obj = Json::String(input.to_string());
353 let result = Arc::<serde_json::Value>::try_from(&json_obj);
354 assert!(result.is_err());
355 }
356
357 #[test]
358 fn test_run_cmd_error_output() {
359 let result = run_cmd("ls /non_existent_directory_12345");
361 assert!(result.is_err());
362 assert!(result.unwrap_err().to_string().contains("run command failed"));
363 }
364}