aprender_contracts_cli/commands/
query.rs1use std::path::Path;
4
5use provable_contracts::query::{self, ContractIndex, QueryParams, SearchMode};
6use provable_contracts::schema::ContractKind;
7
8#[allow(clippy::struct_excessive_bools)]
10pub struct QueryCliParams<'a> {
11 pub contract_dir: &'a Path,
12 pub query_str: &'a str,
13 pub regex: bool,
14 pub literal: bool,
15 pub case_sensitive: bool,
16 pub limit: usize,
17 pub obligation: Option<&'a str>,
18 pub min_score: Option<f64>,
19 pub min_level: Option<String>,
20 pub depends_on: Option<&'a str>,
21 pub depended_by: Option<&'a str>,
22 pub unproven: bool,
23 pub show_score: bool,
24 pub show_graph: bool,
25 pub show_paper: bool,
26 pub show_proof_status: bool,
27 pub show_binding: bool,
28 pub binding_gaps: bool,
29 pub binding: Option<&'a Path>,
30 pub show_diff: bool,
31 pub show_pagerank: bool,
32 pub show_call_sites: bool,
33 pub show_violations: bool,
34 pub show_coverage_map: bool,
35 pub project_filter: Option<&'a str>,
36 pub include_project: Option<&'a Path>,
37 pub tier: Option<u8>,
38 pub class: Option<char>,
39 pub kind: Option<&'a str>,
40 pub all_projects: bool,
41 pub rebuild_index: bool,
42 pub format: &'a str,
43 pub exit_code: bool,
44}
45
46pub fn run(p: &QueryCliParams<'_>) -> Result<(), Box<dyn std::error::Error>> {
47 let index = ContractIndex::from_directory_opts(p.contract_dir, p.rebuild_index)?;
48
49 let mode = if p.regex {
50 SearchMode::Regex
51 } else if p.literal {
52 SearchMode::Literal
53 } else {
54 SearchMode::Semantic
55 };
56
57 let params = QueryParams {
58 query: p.query_str.to_string(),
59 mode,
60 case_sensitive: p.case_sensitive,
61 limit: p.limit,
62 obligation_filter: p.obligation.map(String::from),
63 min_score: p.min_score,
64 depends_on: p.depends_on.map(String::from),
65 depended_by: p.depended_by.map(String::from),
66 unproven_only: p.unproven,
67 show_score: p.show_score,
68 show_graph: p.show_graph,
69 show_paper: p.show_paper,
70 show_proof_status: p.show_proof_status,
71 show_binding: p.show_binding,
72 binding_path: p.binding.map(|b| b.display().to_string()),
73 binding_gaps_only: p.binding_gaps,
74 show_diff: p.show_diff,
75 show_pagerank: p.show_pagerank,
76 show_call_sites: p.show_call_sites,
77 show_violations: p.show_violations,
78 show_coverage_map: p.show_coverage_map,
79 min_level: p.min_level.clone(),
80 project_filter: p.project_filter.map(String::from),
81 include_project: p.include_project.map(|p| p.display().to_string()),
82 tier_filter: p.tier,
83 class_filter: p.class,
84 kind_filter: p.kind.map(parse_kind).transpose()?,
85 all_projects: p.all_projects,
86 };
87
88 let output = query::execute(&index, ¶ms);
89
90 match p.format {
91 "json" => println!("{}", serde_json::to_string_pretty(&output)?),
92 "markdown" => print!("{}", output.to_markdown()),
93 _ => print!("{output}"),
94 }
95
96 if p.exit_code && output.results.is_empty() {
97 return Err("No matching contracts found".into());
98 }
99
100 Ok(())
101}
102
103fn parse_kind(s: &str) -> Result<ContractKind, Box<dyn std::error::Error>> {
104 match s.to_lowercase().as_str() {
105 "kernel" => Ok(ContractKind::Kernel),
106 "registry" => Ok(ContractKind::Registry),
107 "model-family" | "modelfamily" => Ok(ContractKind::ModelFamily),
108 "pattern" => Ok(ContractKind::Pattern),
109 "schema" => Ok(ContractKind::Schema),
110 other => Err(format!(
111 "invalid --kind value '{other}': expected one of \
112 kernel, registry, model-family, pattern, schema"
113 )
114 .into()),
115 }
116}