rust-analyzer-cli 0.4.1

A library and CLI tool built on top of rust-analyzer for codebase navigation
Documentation
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Debug, Serialize, Deserialize)]
pub struct DaemonStatusResponse {
    pub ready: bool,
    pub indexed_at: Option<String>,
    pub workspace_root: String,
    pub process_id: u32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SymbolQueryRequest {
    pub name: String,
    pub kind: String,
    pub exact: bool,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SymbolItem {
    pub name: String,
    pub kind: String,
    pub file: String,
    pub line: u32,
    pub col: u32,
    pub container_name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct OutlineQueryRequest {
    pub file: PathBuf,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct OutlineItem {
    pub name: String,
    pub kind: String,
    pub detail: Option<String>,
    pub line: u32,
    pub col: u32,
    pub end_line: u32,
    pub children: Vec<OutlineItem>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DefinitionQueryRequest {
    pub file: PathBuf,
    pub line: u32,
    pub col: u32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BodyQueryRequest {
    pub file: PathBuf,
    pub line: u32,
    pub col: u32,
    #[serde(default = "default_body_max_lines")]
    pub max_lines: usize,
}

fn default_body_max_lines() -> usize {
    100
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DefinitionItem {
    pub file: String,
    pub line: u32,
    pub col: u32,
    pub end_line: u32,
    pub end_col: u32,
    pub snippet: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub body: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CursorQueryRequest {
    pub file: PathBuf,
    pub line: u32,
    pub col: u32,
    pub mode: String, // "incoming", "outgoing", "references"
    pub depth: u32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CursorItem {
    pub name: String,
    pub kind: String,
    pub file: String,
    pub line: u32,
    pub col: u32,
    pub caller_or_callee: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TypeHierarchyQueryRequest {
    pub file: PathBuf,
    pub line: u32,
    pub col: u32,
    pub mode: String, // "supertypes", "subtypes"
    pub depth: u32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TypeHierarchyItemResult {
    pub name: String,
    pub kind: String,
    pub file: String,
    pub line: u32,
    pub col: u32,
    pub detail: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct BodyItem {
    pub file: String,
    pub line: u32,
    pub col: u32,
    pub end_line: u32,
    pub end_col: u32,
    pub body: String,
    pub total_lines: usize,
    pub is_truncated: bool,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CheckQueryRequest {
    pub target: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CheckDiagnosticItem {
    pub message: String,
    pub level: String, // "error", "warning"
    pub file: Option<String>,
    pub line: Option<u32>,
    pub col: Option<u32>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CheckResponse {
    pub success: bool,
    pub diagnostics: Vec<CheckDiagnosticItem>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_daemon_status_json_roundtrip() {
        let status = DaemonStatusResponse {
            ready: true,
            indexed_at: Some("2026-07-31T10:00:00Z".to_string()),
            workspace_root: "/path/to/workspace".to_string(),
            process_id: 12345,
        };
        let json = serde_json::to_string(&status).unwrap();
        let decoded: DaemonStatusResponse = serde_json::from_str(&json).unwrap();
        assert!(decoded.ready);
        assert_eq!(decoded.process_id, 12345);
        assert_eq!(decoded.workspace_root, "/path/to/workspace");
    }

    #[test]
    fn test_symbol_item_json_roundtrip() {
        let item = SymbolItem {
            name: "ExampleStruct".to_string(),
            kind: "struct".to_string(),
            file: "tests/code_example.rs".to_string(),
            line: 15,
            col: 1,
            container_name: Some("lsp".to_string()),
            body: None,
        };

        let json = serde_json::to_string(&item).unwrap();
        let decoded: SymbolItem = serde_json::from_str(&json).unwrap();
        assert_eq!(decoded.name, "ExampleStruct");
        assert_eq!(decoded.kind, "struct");
    }

    #[test]
    fn test_body_query_request_defaults_to_100_lines() {
        let request: BodyQueryRequest =
            serde_json::from_str(r#"{"file":"tests/code_example.rs","line":15,"col":10}"#).unwrap();
        assert_eq!(request.max_lines, 100);
    }
}