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 {
492 let out = stdout.into();
493 let data = if code == 0 {
494 Self::try_parse_json(&out)
495 } else {
496 None
497 };
498 Self {
499 code,
500 out,
501 err: stderr.into(),
502 data,
503 output: None,
504 }
505 }
506
507 pub fn ok(&self) -> bool {
509 self.code == 0
510 }
511
512 pub fn get_field(&self, name: &str) -> Option<Value> {
514 match name {
515 "code" => Some(Value::Int(self.code)),
516 "ok" => Some(Value::Bool(self.ok())),
517 "out" => Some(Value::String(self.out.clone())),
518 "err" => Some(Value::String(self.err.clone())),
519 "data" => self.data.clone(),
520 _ => None,
521 }
522 }
523
524 fn try_parse_json(s: &str) -> Option<Value> {
526 let trimmed = s.trim();
527 if trimmed.is_empty() {
528 return None;
529 }
530 serde_json::from_str::<serde_json::Value>(trimmed)
531 .ok()
532 .map(json_to_value)
533 }
534}
535
536impl Default for ExecResult {
537 fn default() -> Self {
538 Self::success("")
539 }
540}
541
542pub fn json_to_value(json: serde_json::Value) -> Value {
547 match json {
548 serde_json::Value::Null => Value::Null,
549 serde_json::Value::Bool(b) => Value::Bool(b),
550 serde_json::Value::Number(n) => {
551 if let Some(i) = n.as_i64() {
552 Value::Int(i)
553 } else if let Some(f) = n.as_f64() {
554 Value::Float(f)
555 } else {
556 Value::String(n.to_string())
557 }
558 }
559 serde_json::Value::String(s) => Value::String(s),
560 serde_json::Value::Array(_) | serde_json::Value::Object(_) => Value::Json(json),
562 }
563}
564
565pub fn value_to_json(value: &Value) -> serde_json::Value {
567 match value {
568 Value::Null => serde_json::Value::Null,
569 Value::Bool(b) => serde_json::Value::Bool(*b),
570 Value::Int(i) => serde_json::Value::Number((*i).into()),
571 Value::Float(f) => {
572 serde_json::Number::from_f64(*f)
573 .map(serde_json::Value::Number)
574 .unwrap_or(serde_json::Value::Null)
575 }
576 Value::String(s) => serde_json::Value::String(s.clone()),
577 Value::Json(json) => json.clone(),
578 Value::Blob(blob) => {
579 let mut map = serde_json::Map::new();
580 map.insert("_type".to_string(), serde_json::Value::String("blob".to_string()));
581 map.insert("id".to_string(), serde_json::Value::String(blob.id.clone()));
582 map.insert("size".to_string(), serde_json::Value::Number(blob.size.into()));
583 map.insert("contentType".to_string(), serde_json::Value::String(blob.content_type.clone()));
584 if let Some(hash) = &blob.hash {
585 let hash_hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect();
586 map.insert("hash".to_string(), serde_json::Value::String(hash_hex));
587 }
588 serde_json::Value::Object(map)
589 }
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 #[test]
598 fn success_creates_ok_result() {
599 let result = ExecResult::success("hello world");
600 assert!(result.ok());
601 assert_eq!(result.code, 0);
602 assert_eq!(result.out, "hello world");
603 assert!(result.err.is_empty());
604 }
605
606 #[test]
607 fn failure_creates_non_ok_result() {
608 let result = ExecResult::failure(1, "command not found");
609 assert!(!result.ok());
610 assert_eq!(result.code, 1);
611 assert_eq!(result.err, "command not found");
612 }
613
614 #[test]
615 fn json_stdout_is_parsed() {
616 let result = ExecResult::success(r#"{"count": 42, "items": ["a", "b"]}"#);
618 assert!(result.data.is_some());
619 let data = result.data.unwrap();
620 assert!(matches!(data, Value::Json(_)));
622 if let Value::Json(json) = data {
624 assert_eq!(json.get("count"), Some(&serde_json::json!(42)));
625 assert_eq!(json.get("items"), Some(&serde_json::json!(["a", "b"])));
626 }
627 }
628
629 #[test]
630 fn non_json_stdout_has_no_data() {
631 let result = ExecResult::success("just plain text");
632 assert!(result.data.is_none());
633 }
634
635 #[test]
636 fn get_field_code() {
637 let result = ExecResult::failure(127, "not found");
638 assert_eq!(result.get_field("code"), Some(Value::Int(127)));
639 }
640
641 #[test]
642 fn get_field_ok() {
643 let success = ExecResult::success("hi");
644 let failure = ExecResult::failure(1, "err");
645 assert_eq!(success.get_field("ok"), Some(Value::Bool(true)));
646 assert_eq!(failure.get_field("ok"), Some(Value::Bool(false)));
647 }
648
649 #[test]
650 fn get_field_out_and_err() {
651 let result = ExecResult::from_output(1, "stdout text", "stderr text");
652 assert_eq!(result.get_field("out"), Some(Value::String("stdout text".into())));
653 assert_eq!(result.get_field("err"), Some(Value::String("stderr text".into())));
654 }
655
656 #[test]
657 fn get_field_data() {
658 let result = ExecResult::success(r#"{"key": "value"}"#);
659 let data = result.get_field("data");
660 assert!(data.is_some());
661 }
662
663 #[test]
664 fn get_field_unknown_returns_none() {
665 let result = ExecResult::success("");
666 assert_eq!(result.get_field("nonexistent"), None);
667 }
668
669 #[test]
670 fn success_data_creates_result_with_value() {
671 let value = Value::String("test data".into());
672 let result = ExecResult::success_data(value.clone());
673 assert!(result.ok());
674 assert_eq!(result.data, Some(value));
675 }
676
677 #[test]
678 fn entry_type_variants() {
679 assert_ne!(EntryType::File, EntryType::Directory);
681 assert_ne!(EntryType::Directory, EntryType::Executable);
682 assert_ne!(EntryType::Executable, EntryType::Symlink);
683 }
684
685 #[test]
686 fn to_json_simple_text() {
687 let output = OutputData::text("hello world");
688 assert_eq!(output.to_json(), serde_json::json!("hello world"));
689 }
690
691 #[test]
692 fn to_json_flat_list() {
693 let output = OutputData::nodes(vec![
694 OutputNode::new("file1"),
695 OutputNode::new("file2"),
696 OutputNode::new("file3"),
697 ]);
698 assert_eq!(output.to_json(), serde_json::json!(["file1", "file2", "file3"]));
699 }
700
701 #[test]
702 fn to_json_table() {
703 let output = OutputData::table(
704 vec!["NAME".into(), "SIZE".into(), "TYPE".into()],
705 vec![
706 OutputNode::new("foo.rs").with_cells(vec!["1024".into(), "file".into()]),
707 OutputNode::new("bar/").with_cells(vec!["4096".into(), "dir".into()]),
708 ],
709 );
710 assert_eq!(output.to_json(), serde_json::json!([
711 {"NAME": "foo.rs", "SIZE": "1024", "TYPE": "file"},
712 {"NAME": "bar/", "SIZE": "4096", "TYPE": "dir"},
713 ]));
714 }
715
716 #[test]
717 fn to_json_tree() {
718 let child1 = OutputNode::new("main.rs").with_entry_type(EntryType::File);
719 let child2 = OutputNode::new("utils.rs").with_entry_type(EntryType::File);
720 let subdir = OutputNode::new("lib")
721 .with_entry_type(EntryType::Directory)
722 .with_children(vec![child2]);
723 let root = OutputNode::new("src")
724 .with_entry_type(EntryType::Directory)
725 .with_children(vec![child1, subdir]);
726
727 let output = OutputData::nodes(vec![root]);
728 assert_eq!(output.to_json(), serde_json::json!({
729 "main.rs": null,
730 "lib": {"utils.rs": null},
731 }));
732 }
733
734 #[test]
735 fn to_json_tree_multiple_roots() {
736 let root1 = OutputNode::new("src")
737 .with_entry_type(EntryType::Directory)
738 .with_children(vec![OutputNode::new("main.rs")]);
739 let root2 = OutputNode::new("docs")
740 .with_entry_type(EntryType::Directory)
741 .with_children(vec![OutputNode::new("README.md")]);
742
743 let output = OutputData::nodes(vec![root1, root2]);
744 assert_eq!(output.to_json(), serde_json::json!({
745 "src": {"main.rs": null},
746 "docs": {"README.md": null},
747 }));
748 }
749
750 #[test]
751 fn to_json_empty() {
752 let output = OutputData::new();
753 assert_eq!(output.to_json(), serde_json::json!([]));
754 }
755
756 #[test]
757 fn apply_output_format_clears_sentinel() {
758 let output = OutputData::table(
759 vec!["NAME".into()],
760 vec![OutputNode::new("test")],
761 );
762 let result = ExecResult::with_output(output);
763 assert!(result.output.is_some(), "before: sentinel present");
764
765 let formatted = apply_output_format(result, OutputFormat::Json);
766 assert!(formatted.output.is_none(), "after Json: sentinel cleared");
767 }
768
769 #[test]
770 fn apply_output_format_no_double_encoding() {
771 let output = OutputData::nodes(vec![
774 OutputNode::new("file1"),
775 OutputNode::new("file2"),
776 ]);
777 let result = ExecResult::with_output(output);
778
779 let after_json = apply_output_format(result, OutputFormat::Json);
781 let json_out = after_json.out.clone();
782 assert!(after_json.output.is_none(), "sentinel cleared by Json");
783
784 assert!(after_json.output.is_none());
786 let parsed: serde_json::Value = serde_json::from_str(&json_out).expect("valid JSON");
788 assert_eq!(parsed, serde_json::json!(["file1", "file2"]));
789 }
790}