use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
#[allow(dead_code)]
pub jsonrpc: String, pub method: String,
pub params: Option<serde_json::Value>,
pub id: Option<serde_json::Value>, }
#[derive(Debug, Deserialize)]
pub struct SearchParams {
pub query: String,
pub repo: String,
pub worktree: Option<String>,
pub limit: Option<usize>,
pub threshold: Option<f32>,
pub mode: Option<String>, #[serde(default = "default_deduplicate")]
pub deduplicate: Option<bool>,
#[serde(default)]
pub kind: Option<Vec<String>>,
#[serde(default)]
pub lang: Option<Vec<String>>,
#[serde(default)]
pub include_confidence: Option<bool>,
}
fn default_deduplicate() -> Option<bool> {
Some(true)
}
fn default_budget() -> usize {
6000
}
fn default_max_depth() -> i32 {
2
}
#[derive(Debug, Deserialize)]
pub struct ContextParams {
pub chunk_id: String,
#[serde(default = "default_budget")]
pub budget_tokens: usize,
#[serde(default)]
pub expand: ExpandConfig,
}
#[derive(Debug, Deserialize)]
pub struct ExpandConfig {
#[serde(default)]
pub callers: bool,
#[serde(default)]
pub callees: bool,
#[serde(default)]
pub tests: bool,
#[serde(default)]
pub docs: bool,
#[serde(default)]
pub config: bool,
#[serde(default = "default_max_depth")]
pub max_depth: i32,
#[serde(default)]
pub routes: bool,
#[serde(default)]
pub hooks: bool,
#[serde(default)]
pub jsx_parents: bool,
#[serde(default)]
pub jsx_children: bool,
}
impl Default for ExpandConfig {
fn default() -> Self {
Self {
callers: false,
callees: false,
tests: false,
docs: false,
config: false,
max_depth: 2, routes: false,
hooks: false,
jsx_parents: false,
jsx_children: false,
}
}
}
#[derive(Debug, Deserialize, Default)]
pub struct StatusParams {
pub repo: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct WorktreeStatus {
pub name: String,
pub path: String,
pub file_count: i64,
pub chunk_count: i64,
}
#[derive(Debug, Serialize)]
pub struct RepoStatus {
pub name: String,
pub worktrees: Vec<WorktreeStatus>,
}
#[derive(Debug, Serialize)]
pub struct StatusResult {
pub repos: Vec<RepoStatus>,
pub total_repos: usize,
pub total_files: i64,
pub total_chunks: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<JsonRpcError>,
pub id: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
pub data: Option<serde_json::Value>,
}
impl JsonRpcResponse {
pub fn success(id: serde_json::Value, result: serde_json::Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
result: Some(result),
error: None,
id,
}
}
pub fn error(
id: serde_json::Value,
code: i32,
message: String,
data: Option<serde_json::Value>,
) -> Self {
Self {
jsonrpc: "2.0".to_string(),
result: None,
error: Some(JsonRpcError {
code,
message,
data,
}),
id,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_params_deserialization_minimal() {
let json = r#"{"chunk_id": "12345"}"#;
let params: ContextParams = serde_json::from_str(json).unwrap();
assert_eq!(params.chunk_id, "12345");
assert_eq!(params.budget_tokens, 6000); assert!(!params.expand.callers); assert!(!params.expand.callees);
assert!(!params.expand.tests);
assert!(!params.expand.docs);
assert!(!params.expand.config);
assert_eq!(params.expand.max_depth, 2); assert!(!params.expand.routes);
assert!(!params.expand.hooks);
assert!(!params.expand.jsx_parents);
assert!(!params.expand.jsx_children);
}
#[test]
fn test_context_params_deserialization_full() {
let json = r#"{
"chunk_id": "99999",
"budget_tokens": 8000,
"expand": {
"callers": true,
"callees": true,
"tests": true,
"docs": true,
"config": true,
"max_depth": 5,
"routes": true,
"hooks": true,
"jsx_parents": true,
"jsx_children": true
}
}"#;
let params: ContextParams = serde_json::from_str(json).unwrap();
assert_eq!(params.chunk_id, "99999");
assert_eq!(params.budget_tokens, 8000);
assert!(params.expand.callers);
assert!(params.expand.callees);
assert!(params.expand.tests);
assert!(params.expand.docs);
assert!(params.expand.config);
assert_eq!(params.expand.max_depth, 5);
assert!(params.expand.routes);
assert!(params.expand.hooks);
assert!(params.expand.jsx_parents);
assert!(params.expand.jsx_children);
}
#[test]
fn test_expand_config_defaults() {
let config = ExpandConfig::default();
assert!(!config.callers);
assert!(!config.callees);
assert!(!config.tests);
assert!(!config.docs);
assert!(!config.config);
assert_eq!(config.max_depth, 2); assert!(!config.routes);
assert!(!config.hooks);
assert!(!config.jsx_parents);
assert!(!config.jsx_children);
}
#[test]
fn test_context_params_partial_expand() {
let json = r#"{
"chunk_id": "42",
"expand": {
"callers": true,
"tests": true
}
}"#;
let params: ContextParams = serde_json::from_str(json).unwrap();
assert_eq!(params.chunk_id, "42");
assert_eq!(params.budget_tokens, 6000); assert!(params.expand.callers);
assert!(!params.expand.callees); assert!(params.expand.tests);
assert!(!params.expand.docs); assert_eq!(params.expand.max_depth, 2); }
#[test]
fn test_search_params_with_confidence_true() {
let json = r#"{"query": "test", "repo": "myrepo", "include_confidence": true}"#;
let params: SearchParams = serde_json::from_str(json).unwrap();
assert_eq!(params.include_confidence, Some(true));
}
#[test]
fn test_search_params_with_confidence_false() {
let json = r#"{"query": "test", "repo": "myrepo", "include_confidence": false}"#;
let params: SearchParams = serde_json::from_str(json).unwrap();
assert_eq!(params.include_confidence, Some(false));
}
#[test]
fn test_search_params_without_confidence_field() {
let json = r#"{"query": "test", "repo": "myrepo"}"#;
let params: SearchParams = serde_json::from_str(json).unwrap();
assert_eq!(params.include_confidence, None);
}
}