Skip to main content

sqry_cli/commands/graph/
resolve.rs

1//! `sqry graph resolve <symbol> [--explain] [--json]`
2//!
3//! End-to-end proof point for the Phase 2 binding plane: loads a snapshot
4//! from disk, constructs a `BindingPlane` facade, runs a resolution query,
5//! and prints the outcome. With `--explain` the ordered step trace is rendered
6//! via [`WitnessRendering`].
7//!
8//! # Output contracts
9//!
10//! **Text mode** (default):
11//! ```text
12//! symbol: <name>
13//! outcome: <Debug repr of SymbolResolutionOutcome>
14//!   - node_id=<NodeId> classification=<Classification> kind=<NodeKind>
15//! [blank line + "witness trace:" section when --explain is given]
16//! ```
17//!
18//! **JSON mode** (`--json`):
19//! ```json
20//! {
21//!   "symbol":   "<name>",
22//!   "outcome":  "<serde outcome>",
23//!   "bindings": [
24//!     { "node_id": "...", "classification": "...", "kind": "..." }
25//!   ],
26//!   "explain":  null | { "steps": [...], "candidate_count": N, ... }
27//! }
28//! ```
29//! The JSON shape is the stable external contract for scripting consumers.
30//! Changes to key names or value types are a breaking public-API change.
31//!
32//! Spec: `01_SPEC.md#fr9-end-to-end-proof`
33//! Plan: `03_IMPLEMENTATION_PLAN.md` P2U10
34
35use anyhow::{Context, Result};
36use sqry_core::graph::unified::concurrent::GraphSnapshot;
37use sqry_core::graph::unified::resolution::{FileScope, ResolutionMode, SymbolQuery};
38
39/// Runs the `sqry graph resolve` subcommand.
40///
41/// # Arguments
42///
43/// - `snapshot` — immutable MVCC snapshot, already loaded by the caller.
44/// - `symbol`   — raw symbol text from the CLI argument.
45/// - `explain`  — when `true`, the ordered witness step trace is appended
46///   to the text output (or included as the `explain` field in JSON output).
47/// - `json`     — when `true`, all output is emitted as a JSON document whose
48///   shape is the stable external contract documented in the module doc.
49///
50/// # Errors
51///
52/// Returns an error only when JSON serialization fails (an internal invariant
53/// violation — `serde_json` should never fail on our well-typed structs).
54pub fn run(snapshot: &GraphSnapshot, symbol: &str, explain: bool, json: bool) -> Result<()> {
55    let query = SymbolQuery {
56        symbol,
57        file_scope: FileScope::Any,
58        mode: ResolutionMode::AllowSuffixCandidates,
59    };
60
61    let plane = snapshot.binding_plane();
62    let resolution = plane.resolve(&query);
63
64    if json {
65        print_json(&plane, symbol, &resolution, explain)?;
66    } else {
67        print_text(&plane, symbol, &resolution, explain);
68    }
69
70    Ok(())
71}
72
73/// Renders the resolution result as human-readable text.
74fn print_text(
75    plane: &sqry_core::graph::unified::bind::BindingPlane<'_>,
76    symbol: &str,
77    resolution: &sqry_core::graph::unified::bind::BindingResolution,
78    explain: bool,
79) {
80    println!("symbol: {symbol}");
81    println!("outcome: {:?}", resolution.result.outcome);
82    for binding in &resolution.result.bindings {
83        println!(
84            "  - node_id={:?} classification={:?} kind={:?}",
85            binding.node_id, binding.classification, binding.kind
86        );
87    }
88    if explain {
89        let rendering = plane.explain(resolution);
90        println!("\nwitness trace:");
91        print!("{}", rendering.text);
92    }
93}
94
95/// Renders the resolution result as a stable JSON document.
96///
97/// # Errors
98///
99/// Returns an error if `serde_json::to_string_pretty` fails (should not occur
100/// for this well-typed document).
101fn print_json(
102    plane: &sqry_core::graph::unified::bind::BindingPlane<'_>,
103    symbol: &str,
104    resolution: &sqry_core::graph::unified::bind::BindingResolution,
105    explain: bool,
106) -> Result<()> {
107    let bindings: Vec<serde_json::Value> = resolution
108        .result
109        .bindings
110        .iter()
111        .map(|b| {
112            serde_json::json!({
113                "node_id":        format!("{:?}", b.node_id),
114                "classification": format!("{:?}", b.classification),
115                "kind":           format!("{:?}", b.kind),
116            })
117        })
118        .collect();
119
120    let explain_value: serde_json::Value = if explain {
121        let rendering = plane.explain(resolution);
122        rendering.json.clone()
123    } else {
124        serde_json::Value::Null
125    };
126
127    let outcome_value = serde_json::to_value(&resolution.result.outcome)
128        .unwrap_or_else(|_| serde_json::Value::String(format!("{:?}", resolution.result.outcome)));
129
130    let doc = serde_json::json!({
131        "symbol":   symbol,
132        "outcome":  outcome_value,
133        "bindings": bindings,
134        "explain":  explain_value,
135    });
136
137    println!(
138        "{}",
139        serde_json::to_string_pretty(&doc).context("JSON serialization failed")?
140    );
141    Ok(())
142}