aprender_contracts_cli/commands/
pipeline.rs1use std::path::Path;
2
3use provable_contracts::pipeline::{self, IssueSeverity};
4use serde_json::Value;
5
6use crate::json_obj::obj;
7
8pub fn run(path: &Path, format: &str) -> Result<(), Box<dyn std::error::Error>> {
10 let pipeline = pipeline::parse_pipeline(path)?;
11 let issues = pipeline::validate_pipeline(&pipeline);
12
13 let errors: Vec<_> = issues
14 .iter()
15 .filter(|i| i.severity == IssueSeverity::Error)
16 .collect();
17 let warnings: Vec<_> = issues
18 .iter()
19 .filter(|i| i.severity == IssueSeverity::Warning)
20 .collect();
21
22 match format {
23 "json" => print_json(&pipeline, &issues),
24 _ => print_text(&pipeline, &errors, &warnings),
25 }
26
27 if !errors.is_empty() {
28 return Err(format!("{} error(s) in pipeline", errors.len()).into());
29 }
30
31 Ok(())
32}
33
34fn print_text(
35 pipeline: &pipeline::PipelineContract,
36 errors: &[&pipeline::PipelineIssue],
37 warnings: &[&pipeline::PipelineIssue],
38) {
39 println!(
40 "Pipeline: {} (v{})",
41 pipeline.metadata.description, pipeline.metadata.version
42 );
43 println!();
44
45 let stage_names = collect_stage_display(&pipeline.stages, 0);
47 println!("Stages ({}):", stage_names.len());
48 for (indent, name, repo, contract) in &stage_names {
49 let prefix = " ".repeat(*indent + 1);
50 let repo_str = repo.as_deref().unwrap_or("—");
51 let contract_str = contract.as_deref().unwrap_or("—");
52 println!("{prefix}{name} [{repo_str}] → {contract_str}");
53 }
54 println!();
55
56 println!(
58 "Cross-boundary obligations ({}):",
59 pipeline.cross_boundary_obligations.len()
60 );
61 for ob in &pipeline.cross_boundary_obligations {
62 println!(
63 " {} {} → {}: {}",
64 ob.id, ob.from_stage, ob.to_stage, ob.property
65 );
66 }
67 println!();
68
69 if let Some(ref perf) = pipeline.performance_contract {
71 println!("Performance:");
72 if let Some(ref r) = perf.roofline {
73 println!(" Roofline: {r}");
74 }
75 if let Some(ref p) = perf.prefill_bound {
76 println!(" Prefill: {p}");
77 }
78 if let Some(ref d) = perf.decode_bound {
79 println!(" Decode: {d}");
80 }
81 println!();
82 }
83
84 if errors.is_empty() && warnings.is_empty() {
86 println!("Validation: PASS (0 errors, 0 warnings)");
87 } else {
88 for e in errors {
89 println!(" [ERROR] {}", e.message);
90 }
91 for w in warnings {
92 println!(" [WARN] {}", w.message);
93 }
94 println!(
95 "\nValidation: {} ({} error(s), {} warning(s))",
96 if errors.is_empty() { "PASS" } else { "FAIL" },
97 errors.len(),
98 warnings.len()
99 );
100 }
101}
102
103fn print_json(pipeline: &pipeline::PipelineContract, issues: &[pipeline::PipelineIssue]) {
104 let error_count = issues
105 .iter()
106 .filter(|i| i.severity == IssueSeverity::Error)
107 .count();
108 let warning_count = issues
109 .iter()
110 .filter(|i| i.severity == IssueSeverity::Warning)
111 .count();
112 let issue_arr: Vec<Value> = issues
113 .iter()
114 .map(|i| {
115 obj([
116 (
117 "severity",
118 Value::from(match i.severity {
119 IssueSeverity::Error => "error",
120 IssueSeverity::Warning => "warning",
121 }),
122 ),
123 ("message", Value::from(i.message.clone())),
124 ])
125 })
126 .collect();
127
128 let output = obj([
129 (
130 "description",
131 Value::from(pipeline.metadata.description.clone()),
132 ),
133 ("version", Value::from(pipeline.metadata.version.clone())),
134 ("stage_count", Value::from(pipeline.stages.len())),
135 (
136 "obligation_count",
137 Value::from(pipeline.cross_boundary_obligations.len()),
138 ),
139 ("error_count", Value::from(error_count)),
140 ("warning_count", Value::from(warning_count)),
141 ("issues", Value::Array(issue_arr)),
142 ]);
143
144 println!(
145 "{}",
146 serde_json::to_string_pretty(&output).unwrap_or_default()
147 );
148}
149
150fn collect_stage_display(
151 stages: &[pipeline::PipelineStage],
152 depth: usize,
153) -> Vec<(usize, String, Option<String>, Option<String>)> {
154 let mut result = Vec::new();
155 for s in stages {
156 result.push((depth, s.name.clone(), s.repo.clone(), s.contract.clone()));
157 if !s.substages.is_empty() {
158 result.extend(collect_stage_display(&s.substages, depth + 1));
159 }
160 }
161 result
162}