Skip to main content

aprender_contracts_cli/commands/
query.rs

1//! `pv query` — Search contracts by intent, regex, or literal match.
2
3use std::path::Path;
4
5use provable_contracts::query::{self, ContractIndex, QueryParams, SearchMode};
6use provable_contracts::schema::ContractKind;
7
8/// All parameters needed to execute a query from the CLI.
9#[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, &params);
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}