diffai_core/
output.rs

1use anyhow::Result;
2use diffx_core::format_diff_output;
3
4use crate::types::{DiffResult, OutputFormat};
5
6// ============================================================================
7// UTILITY FUNCTIONS - FOR INTERNAL USE ONLY
8// ============================================================================
9// These functions are public only for CLI and language bindings.
10// External users should use the main diff() function.
11
12/// Format output to string - redirects to format_diff_results for DiffResult
13pub fn format_output(results: &[DiffResult], format: OutputFormat) -> Result<String> {
14    format_diff_results(results, format)
15}
16
17/// DiffResult専用のフォーマット関数(diffx-coreと統合)
18pub fn format_diff_results(results: &[DiffResult], format: OutputFormat) -> Result<String> {
19    match format {
20        OutputFormat::Json => {
21            // JSON形式では全てを統一された配列として出力
22            Ok(serde_json::to_string_pretty(results)?)
23        }
24        OutputFormat::Yaml => {
25            // YAML形式でも全てを統一して出力
26            Ok(serde_yaml::to_string(results)?)
27        }
28        OutputFormat::Diffai => {
29            // Diffai形式では基本型をdiffx-coreでフォーマット、ML型は手動フォーマット
30            let base_results: Vec<diffx_core::DiffResult> = results
31                .iter()
32                .filter_map(|r| match r {
33                    DiffResult::Added(path, value) => {
34                        Some(diffx_core::DiffResult::Added(path.clone(), value.clone()))
35                    }
36                    DiffResult::Removed(path, value) => {
37                        Some(diffx_core::DiffResult::Removed(path.clone(), value.clone()))
38                    }
39                    DiffResult::Modified(path, old, new) => Some(diffx_core::DiffResult::Modified(
40                        path.clone(),
41                        old.clone(),
42                        new.clone(),
43                    )),
44                    DiffResult::TypeChanged(path, old, new) => Some(
45                        diffx_core::DiffResult::TypeChanged(path.clone(), old.clone(), new.clone()),
46                    ),
47                    _ => None,
48                })
49                .collect();
50
51            let mut output = String::new();
52
53            // 基本型があればdiffx-coreでフォーマット
54            if !base_results.is_empty() {
55                let base_format = format.to_base_format();
56                let formatted = format_diff_output(&base_results, base_format, None)?;
57                output.push_str(&formatted);
58            }
59
60            // AI/ML専用型を追加(data_summaryの変更は既にJSON diffで表示されるため除外)
61            let ml_results: Vec<&DiffResult> = results
62                .iter()
63                .filter(|r| {
64                    match r {
65                        // TensorStatsChanged: data_summaryパスは除外(重複回避)
66                        DiffResult::TensorStatsChanged(path, _, _) => {
67                            !path.contains("data_summary")
68                        }
69                        DiffResult::TensorShapeChanged(_, _, _)
70                        | DiffResult::TensorDataChanged(_, _, _)
71                        | DiffResult::ModelArchitectureChanged(_, _, _)
72                        | DiffResult::WeightSignificantChange(_, _)
73                        | DiffResult::ActivationFunctionChanged(_, _, _)
74                        | DiffResult::LearningRateChanged(_, _, _)
75                        | DiffResult::OptimizerChanged(_, _, _)
76                        | DiffResult::LossChange(_, _, _)
77                        | DiffResult::AccuracyChange(_, _, _)
78                        | DiffResult::ModelVersionChanged(_, _, _) => true,
79                        _ => false,
80                    }
81                })
82                .collect();
83
84            if !ml_results.is_empty() {
85                if !output.is_empty() && !output.ends_with('\n') {
86                    output.push('\n');
87                }
88                output.push('\n');
89
90                for result in &ml_results {
91                    match result {
92                        DiffResult::ModelArchitectureChanged(path, old, new) => {
93                            output.push_str(&format!("  ~ {path}: {old} -> {new}\n"));
94                        }
95                        DiffResult::TensorShapeChanged(path, old_shape, new_shape) => {
96                            output.push_str(&format!(
97                                "  ~ {path} shape: {old_shape:?} -> {new_shape:?}\n"
98                            ));
99                        }
100                        DiffResult::TensorStatsChanged(path, old_stats, new_stats) => {
101                            output.push_str(&format!(
102                                "  ~ {} stats: mean {:.3} -> {:.3}\n",
103                                path, old_stats.mean, new_stats.mean
104                            ));
105                        }
106                        _ => {
107                            // その他のML型もサポート
108                            output.push_str(&format!(
109                                "  ~ ML analysis: {}\n",
110                                serde_json::to_string(result)?
111                            ));
112                        }
113                    }
114                }
115            }
116
117            Ok(output)
118        }
119    }
120}