1use crate::ast::Value;
25use serde::Serialize;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
36#[serde(rename_all = "lowercase")]
37pub enum EntryType {
38 #[default]
40 Text,
41 File,
43 Directory,
45 Executable,
47 Symlink,
49}
50
51#[derive(Debug, Clone, PartialEq, Default, Serialize)]
87pub struct OutputNode {
88 pub name: String,
90 pub entry_type: EntryType,
92 pub text: Option<String>,
94 pub cells: Vec<String>,
96 pub children: Vec<OutputNode>,
98}
99
100impl OutputNode {
101 pub fn new(name: impl Into<String>) -> Self {
103 Self {
104 name: name.into(),
105 ..Default::default()
106 }
107 }
108
109 pub fn text(content: impl Into<String>) -> Self {
111 Self {
112 text: Some(content.into()),
113 ..Default::default()
114 }
115 }
116
117 pub fn with_entry_type(mut self, entry_type: EntryType) -> Self {
119 self.entry_type = entry_type;
120 self
121 }
122
123 pub fn with_cells(mut self, cells: Vec<String>) -> Self {
125 self.cells = cells;
126 self
127 }
128
129 pub fn with_children(mut self, children: Vec<OutputNode>) -> Self {
131 self.children = children;
132 self
133 }
134
135 pub fn with_text(mut self, text: impl Into<String>) -> Self {
137 self.text = Some(text.into());
138 self
139 }
140
141 pub fn is_text_only(&self) -> bool {
143 self.text.is_some() && self.name.is_empty() && self.cells.is_empty() && self.children.is_empty()
144 }
145
146 pub fn has_children(&self) -> bool {
148 !self.children.is_empty()
149 }
150
151 pub fn display_name(&self) -> &str {
153 if self.name.is_empty() {
154 self.text.as_deref().unwrap_or("")
155 } else {
156 &self.name
157 }
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Default, Serialize)]
175pub struct OutputData {
176 pub headers: Option<Vec<String>>,
178 pub root: Vec<OutputNode>,
180}
181
182impl OutputData {
183 pub fn new() -> Self {
185 Self::default()
186 }
187
188 pub fn text(content: impl Into<String>) -> Self {
192 Self {
193 headers: None,
194 root: vec![OutputNode::text(content)],
195 }
196 }
197
198 pub fn nodes(nodes: Vec<OutputNode>) -> Self {
200 Self {
201 headers: None,
202 root: nodes,
203 }
204 }
205
206 pub fn table(headers: Vec<String>, nodes: Vec<OutputNode>) -> Self {
208 Self {
209 headers: Some(headers),
210 root: nodes,
211 }
212 }
213
214 pub fn with_headers(mut self, headers: Vec<String>) -> Self {
216 self.headers = Some(headers);
217 self
218 }
219
220 pub fn is_simple_text(&self) -> bool {
222 self.root.len() == 1 && self.root[0].is_text_only()
223 }
224
225 pub fn is_flat(&self) -> bool {
227 self.root.iter().all(|n| !n.has_children())
228 }
229
230 pub fn is_tabular(&self) -> bool {
232 self.root.iter().any(|n| !n.cells.is_empty())
233 }
234
235 pub fn as_text(&self) -> Option<&str> {
237 if self.is_simple_text() {
238 self.root[0].text.as_deref()
239 } else {
240 None
241 }
242 }
243
244 pub fn to_canonical_string(&self) -> String {
253 if let Some(text) = self.as_text() {
254 return text.to_string();
255 }
256
257 if self.is_flat() {
259 return self.root.iter()
260 .map(|n| {
261 if n.cells.is_empty() {
262 n.display_name().to_string()
263 } else {
264 let mut parts = vec![n.display_name().to_string()];
266 parts.extend(n.cells.iter().cloned());
267 parts.join("\t")
268 }
269 })
270 .collect::<Vec<_>>()
271 .join("\n");
272 }
273
274 fn format_node(node: &OutputNode) -> String {
276 if node.children.is_empty() {
277 node.name.clone()
278 } else {
279 let children: Vec<String> = node.children.iter()
280 .map(format_node)
281 .collect();
282 format!("{}/{{{}}}", node.name, children.join(","))
283 }
284 }
285
286 self.root.iter()
287 .map(format_node)
288 .collect::<Vec<_>>()
289 .join("\n")
290 }
291
292 pub fn to_json(&self) -> serde_json::Value {
303 if let Some(text) = self.as_text() {
305 return serde_json::Value::String(text.to_string());
306 }
307
308 if let Some(ref headers) = self.headers {
310 let rows: Vec<serde_json::Value> = self.root.iter().map(|node| {
311 let mut map = serde_json::Map::new();
312 if let Some(first) = headers.first() {
314 map.insert(first.clone(), serde_json::Value::String(node.name.clone()));
315 }
316 for (header, cell) in headers.iter().skip(1).zip(node.cells.iter()) {
318 map.insert(header.clone(), serde_json::Value::String(cell.clone()));
319 }
320 serde_json::Value::Object(map)
321 }).collect();
322 return serde_json::Value::Array(rows);
323 }
324
325 if !self.is_flat() {
327 fn node_to_json(node: &OutputNode) -> serde_json::Value {
328 if node.children.is_empty() {
329 serde_json::Value::Null
330 } else {
331 let mut map = serde_json::Map::new();
332 for child in &node.children {
333 map.insert(child.name.clone(), node_to_json(child));
334 }
335 serde_json::Value::Object(map)
336 }
337 }
338
339 if self.root.len() == 1 {
341 return node_to_json(&self.root[0]);
342 }
343 let mut map = serde_json::Map::new();
345 for node in &self.root {
346 map.insert(node.name.clone(), node_to_json(node));
347 }
348 return serde_json::Value::Object(map);
349 }
350
351 let items: Vec<serde_json::Value> = self.root.iter()
353 .map(|n| serde_json::Value::String(n.display_name().to_string()))
354 .collect();
355 serde_json::Value::Array(items)
356 }
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub enum OutputFormat {
366 Json,
368}
369
370pub fn apply_output_format(mut result: ExecResult, format: OutputFormat) -> ExecResult {
376 if result.output.is_none() && result.out.is_empty() {
377 return result;
378 }
379 match format {
380 OutputFormat::Json => {
381 let json_str = if let Some(ref output) = result.output {
382 serde_json::to_string_pretty(&output.to_json())
383 .unwrap_or_else(|_| "null".to_string())
384 } else {
385 serde_json::to_string(&result.out)
387 .unwrap_or_else(|_| "null".to_string())
388 };
389 result.out = json_str;
390 result.output = None;
392 result
393 }
394 }
395}
396
397#[derive(Debug, Clone, PartialEq)]
406pub struct ExecResult {
407 pub code: i64,
409 pub out: String,
411 pub err: String,
413 pub data: Option<Value>,
415 pub output: Option<OutputData>,
417}
418
419impl ExecResult {
420 pub fn success(out: impl Into<String>) -> Self {
422 let out = out.into();
423 let data = Self::try_parse_json(&out);
424 Self {
425 code: 0,
426 out,
427 err: String::new(),
428 data,
429 output: None,
430 }
431 }
432
433 pub fn with_output(output: OutputData) -> Self {
438 let out = output.to_canonical_string();
439 let data = Self::try_parse_json(&out);
440 Self {
441 code: 0,
442 out,
443 err: String::new(),
444 data,
445 output: Some(output),
446 }
447 }
448
449 pub fn success_data(data: Value) -> Self {
451 let out = value_to_json(&data).to_string();
452 Self {
453 code: 0,
454 out,
455 err: String::new(),
456 data: Some(data),
457 output: None,
458 }
459 }
460
461 pub fn success_with_data(out: impl Into<String>, data: Value) -> Self {
470 Self {
471 code: 0,
472 out: out.into(),
473 err: String::new(),
474 data: Some(data),
475 output: None,
476 }
477 }
478
479 pub fn failure(code: i64, err: impl Into<String>) -> Self {
481 Self {
482 code,
483 out: String::new(),
484 err: err.into(),
485 data: None,
486 output: None,
487 }
488 }
489
490 pub fn from_output(code: i64, stdout: impl Into<String>, stderr: impl Into<String>) -> Self {
499 let out = stdout.into();
500 let data = if code == 0 {
501 Self::try_parse_json(&out)
502 } else {
503 None
504 };
505 Self {
506 code,
507 out,
508 err: stderr.into(),
509 data,
510 output: None,
511 }
512 }
513
514 pub fn ok(&self) -> bool {
516 self.code == 0
517 }
518
519 pub fn get_field(&self, name: &str) -> Option<Value> {
521 match name {
522 "code" => Some(Value::Int(self.code)),
523 "ok" => Some(Value::Bool(self.ok())),
524 "out" => Some(Value::String(self.out.clone())),
525 "err" => Some(Value::String(self.err.clone())),
526 "data" => self.data.clone(),
527 _ => None,
528 }
529 }
530
531 fn try_parse_json(s: &str) -> Option<Value> {
533 let trimmed = s.trim();
534 if trimmed.is_empty() {
535 return None;
536 }
537 serde_json::from_str::<serde_json::Value>(trimmed)
538 .ok()
539 .map(json_to_value)
540 }
541}
542
543impl Default for ExecResult {
544 fn default() -> Self {
545 Self::success("")
546 }
547}
548
549pub fn json_to_value(json: serde_json::Value) -> Value {
554 match json {
555 serde_json::Value::Null => Value::Null,
556 serde_json::Value::Bool(b) => Value::Bool(b),
557 serde_json::Value::Number(n) => {
558 if let Some(i) = n.as_i64() {
559 Value::Int(i)
560 } else if let Some(f) = n.as_f64() {
561 Value::Float(f)
562 } else {
563 Value::String(n.to_string())
564 }
565 }
566 serde_json::Value::String(s) => Value::String(s),
567 serde_json::Value::Array(_) | serde_json::Value::Object(_) => Value::Json(json),
569 }
570}
571
572pub fn value_to_json(value: &Value) -> serde_json::Value {
574 match value {
575 Value::Null => serde_json::Value::Null,
576 Value::Bool(b) => serde_json::Value::Bool(*b),
577 Value::Int(i) => serde_json::Value::Number((*i).into()),
578 Value::Float(f) => {
579 serde_json::Number::from_f64(*f)
580 .map(serde_json::Value::Number)
581 .unwrap_or(serde_json::Value::Null)
582 }
583 Value::String(s) => serde_json::Value::String(s.clone()),
584 Value::Json(json) => json.clone(),
585 Value::Blob(blob) => {
586 let mut map = serde_json::Map::new();
587 map.insert("_type".to_string(), serde_json::Value::String("blob".to_string()));
588 map.insert("id".to_string(), serde_json::Value::String(blob.id.clone()));
589 map.insert("size".to_string(), serde_json::Value::Number(blob.size.into()));
590 map.insert("contentType".to_string(), serde_json::Value::String(blob.content_type.clone()));
591 if let Some(hash) = &blob.hash {
592 let hash_hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect();
593 map.insert("hash".to_string(), serde_json::Value::String(hash_hex));
594 }
595 serde_json::Value::Object(map)
596 }
597 }
598}
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603
604 #[test]
605 fn success_creates_ok_result() {
606 let result = ExecResult::success("hello world");
607 assert!(result.ok());
608 assert_eq!(result.code, 0);
609 assert_eq!(result.out, "hello world");
610 assert!(result.err.is_empty());
611 }
612
613 #[test]
614 fn failure_creates_non_ok_result() {
615 let result = ExecResult::failure(1, "command not found");
616 assert!(!result.ok());
617 assert_eq!(result.code, 1);
618 assert_eq!(result.err, "command not found");
619 }
620
621 #[test]
622 fn json_stdout_is_parsed() {
623 let result = ExecResult::success(r#"{"count": 42, "items": ["a", "b"]}"#);
625 assert!(result.data.is_some());
626 let data = result.data.unwrap();
627 assert!(matches!(data, Value::Json(_)));
629 if let Value::Json(json) = data {
631 assert_eq!(json.get("count"), Some(&serde_json::json!(42)));
632 assert_eq!(json.get("items"), Some(&serde_json::json!(["a", "b"])));
633 }
634 }
635
636 #[test]
637 fn non_json_stdout_has_no_data() {
638 let result = ExecResult::success("just plain text");
639 assert!(result.data.is_none());
640 }
641
642 #[test]
643 fn get_field_code() {
644 let result = ExecResult::failure(127, "not found");
645 assert_eq!(result.get_field("code"), Some(Value::Int(127)));
646 }
647
648 #[test]
649 fn get_field_ok() {
650 let success = ExecResult::success("hi");
651 let failure = ExecResult::failure(1, "err");
652 assert_eq!(success.get_field("ok"), Some(Value::Bool(true)));
653 assert_eq!(failure.get_field("ok"), Some(Value::Bool(false)));
654 }
655
656 #[test]
657 fn get_field_out_and_err() {
658 let result = ExecResult::from_output(1, "stdout text", "stderr text");
659 assert_eq!(result.get_field("out"), Some(Value::String("stdout text".into())));
660 assert_eq!(result.get_field("err"), Some(Value::String("stderr text".into())));
661 }
662
663 #[test]
664 fn get_field_data() {
665 let result = ExecResult::success(r#"{"key": "value"}"#);
666 let data = result.get_field("data");
667 assert!(data.is_some());
668 }
669
670 #[test]
671 fn get_field_unknown_returns_none() {
672 let result = ExecResult::success("");
673 assert_eq!(result.get_field("nonexistent"), None);
674 }
675
676 #[test]
677 fn success_data_creates_result_with_value() {
678 let value = Value::String("test data".into());
679 let result = ExecResult::success_data(value.clone());
680 assert!(result.ok());
681 assert_eq!(result.data, Some(value));
682 }
683
684 #[test]
685 fn entry_type_variants() {
686 assert_ne!(EntryType::File, EntryType::Directory);
688 assert_ne!(EntryType::Directory, EntryType::Executable);
689 assert_ne!(EntryType::Executable, EntryType::Symlink);
690 }
691
692 #[test]
693 fn to_json_simple_text() {
694 let output = OutputData::text("hello world");
695 assert_eq!(output.to_json(), serde_json::json!("hello world"));
696 }
697
698 #[test]
699 fn to_json_flat_list() {
700 let output = OutputData::nodes(vec![
701 OutputNode::new("file1"),
702 OutputNode::new("file2"),
703 OutputNode::new("file3"),
704 ]);
705 assert_eq!(output.to_json(), serde_json::json!(["file1", "file2", "file3"]));
706 }
707
708 #[test]
709 fn to_json_table() {
710 let output = OutputData::table(
711 vec!["NAME".into(), "SIZE".into(), "TYPE".into()],
712 vec![
713 OutputNode::new("foo.rs").with_cells(vec!["1024".into(), "file".into()]),
714 OutputNode::new("bar/").with_cells(vec!["4096".into(), "dir".into()]),
715 ],
716 );
717 assert_eq!(output.to_json(), serde_json::json!([
718 {"NAME": "foo.rs", "SIZE": "1024", "TYPE": "file"},
719 {"NAME": "bar/", "SIZE": "4096", "TYPE": "dir"},
720 ]));
721 }
722
723 #[test]
724 fn to_json_tree() {
725 let child1 = OutputNode::new("main.rs").with_entry_type(EntryType::File);
726 let child2 = OutputNode::new("utils.rs").with_entry_type(EntryType::File);
727 let subdir = OutputNode::new("lib")
728 .with_entry_type(EntryType::Directory)
729 .with_children(vec![child2]);
730 let root = OutputNode::new("src")
731 .with_entry_type(EntryType::Directory)
732 .with_children(vec![child1, subdir]);
733
734 let output = OutputData::nodes(vec![root]);
735 assert_eq!(output.to_json(), serde_json::json!({
736 "main.rs": null,
737 "lib": {"utils.rs": null},
738 }));
739 }
740
741 #[test]
742 fn to_json_tree_multiple_roots() {
743 let root1 = OutputNode::new("src")
744 .with_entry_type(EntryType::Directory)
745 .with_children(vec![OutputNode::new("main.rs")]);
746 let root2 = OutputNode::new("docs")
747 .with_entry_type(EntryType::Directory)
748 .with_children(vec![OutputNode::new("README.md")]);
749
750 let output = OutputData::nodes(vec![root1, root2]);
751 assert_eq!(output.to_json(), serde_json::json!({
752 "src": {"main.rs": null},
753 "docs": {"README.md": null},
754 }));
755 }
756
757 #[test]
758 fn to_json_empty() {
759 let output = OutputData::new();
760 assert_eq!(output.to_json(), serde_json::json!([]));
761 }
762
763 #[test]
764 fn apply_output_format_clears_sentinel() {
765 let output = OutputData::table(
766 vec!["NAME".into()],
767 vec![OutputNode::new("test")],
768 );
769 let result = ExecResult::with_output(output);
770 assert!(result.output.is_some(), "before: sentinel present");
771
772 let formatted = apply_output_format(result, OutputFormat::Json);
773 assert!(formatted.output.is_none(), "after Json: sentinel cleared");
774 }
775
776 #[test]
777 fn apply_output_format_no_double_encoding() {
778 let output = OutputData::nodes(vec![
781 OutputNode::new("file1"),
782 OutputNode::new("file2"),
783 ]);
784 let result = ExecResult::with_output(output);
785
786 let after_json = apply_output_format(result, OutputFormat::Json);
788 let json_out = after_json.out.clone();
789 assert!(after_json.output.is_none(), "sentinel cleared by Json");
790
791 assert!(after_json.output.is_none());
793 let parsed: serde_json::Value = serde_json::from_str(&json_out).expect("valid JSON");
795 assert_eq!(parsed, serde_json::json!(["file1", "file2"]));
796 }
797}