use dunce;
use serde::Serialize;
use std::path::Path;
use crate::content::FileEntry;
use crate::stats::Stats;
pub mod error_codes {
pub const FILE_NOT_FOUND: &str = "file_not_found";
pub const PERMISSION_DENIED: &str = "permission_denied";
pub const BINARY_FILE: &str = "binary_file";
pub const FILE_TOO_LARGE: &str = "file_too_large";
pub const ENCODING_ERROR: &str = "encoding_error";
pub const INVALID_PATTERN: &str = "invalid_pattern";
pub const NO_FILES_MATCHED: &str = "no_files_matched";
pub const OUTPUT_EXISTS: &str = "output_exists";
pub const GIT_ERROR: &str = "git_error";
pub const CONFIG_ERROR: &str = "config_error";
pub const CLIPBOARD_ERROR: &str = "clipboard_error";
pub const IO_ERROR: &str = "io_error";
pub const JSON_ERROR: &str = "json_error";
pub const WALK_ERROR: &str = "walk_error";
pub const IGNORE_ERROR: &str = "ignore_error";
}
#[derive(Serialize, Debug)]
#[serde(tag = "status")]
pub enum JsonResponse {
#[serde(rename = "success")]
Success(SuccessResponse),
#[serde(rename = "error")]
Error(ErrorResponse),
#[serde(rename = "partial")]
Partial(PartialResponse),
}
#[derive(Serialize, Debug)]
pub struct SuccessResponse {
pub data: ResponseData,
pub stats: StatsJson,
}
#[derive(Serialize, Debug)]
#[serde(untagged)]
pub enum ResponseData {
Context(ContextOutput),
FileList(Vec<FileInfo>),
Tree(TreeOutput),
}
#[derive(Serialize, Debug)]
pub struct ContextOutput {
pub content: String,
pub format: String,
pub files: Vec<FileInfo>,
}
#[derive(Serialize, Debug)]
pub struct TreeOutput {
pub tree: String,
}
fn is_zero(n: &usize) -> bool {
*n == 0
}
#[derive(Serialize, Debug, Clone)]
pub struct FileInfo {
pub path: String,
pub extension: String,
pub size_bytes: u64,
#[serde(skip_serializing_if = "is_zero")]
pub line_count: usize,
pub truncated: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub truncated_lines: Option<usize>,
}
impl FileInfo {
pub fn try_from_path(path: &Path) -> Result<Self, std::io::Error> {
let metadata = std::fs::metadata(path)?;
let extension = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_string();
let canonical = dunce::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
let relative_path = std::env::current_dir()
.ok()
.and_then(|cwd| canonical.strip_prefix(&cwd).ok().map(|p| p.to_path_buf()))
.unwrap_or(canonical);
Ok(Self {
path: relative_path.to_string_lossy().to_string(),
extension,
size_bytes: metadata.len(),
line_count: 0, truncated: false,
truncated_lines: None,
})
}
}
impl From<&FileEntry> for FileInfo {
fn from(entry: &FileEntry) -> Self {
Self::from_entry(entry, false)
}
}
impl FileInfo {
pub fn from_entry(entry: &FileEntry, absolute: bool) -> Self {
Self {
path: if absolute {
entry.absolute_path.to_string_lossy().to_string()
} else {
entry.relative_path.clone()
},
extension: entry.extension.clone(),
size_bytes: entry.original_bytes as u64,
line_count: entry.original_lines,
truncated: entry.truncated,
truncated_lines: if entry.truncated {
Some(entry.truncated_lines)
} else {
None
},
}
}
}
#[derive(Serialize, Debug, Clone)]
pub struct StatsJson {
pub file_count: usize,
pub total_lines: usize,
pub total_bytes: usize,
pub truncated_count: usize,
pub skipped_count: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_estimate: Option<usize>,
pub duration_ms: u64,
}
impl StatsJson {
pub fn new(file_count: usize) -> Self {
Self {
file_count,
total_lines: 0,
total_bytes: 0,
truncated_count: 0,
skipped_count: 0,
token_estimate: None,
duration_ms: 0,
}
}
}
impl From<&Stats> for StatsJson {
fn from(stats: &Stats) -> Self {
Self {
file_count: stats.file_count,
total_lines: stats.total_lines,
total_bytes: stats.total_bytes,
truncated_count: stats.truncated_count,
skipped_count: stats.skipped_count,
token_estimate: stats.token_estimate,
duration_ms: stats.duration_ms,
}
}
}
#[derive(Serialize, Debug)]
pub struct ErrorResponse {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub input: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suggestion: Option<String>,
pub transient: bool,
pub exit_code: i32,
}
#[derive(Serialize, Debug)]
pub struct PartialResponse {
pub data: ResponseData,
pub stats: StatsJson,
pub errors: Vec<FileError>,
}
#[derive(Serialize, Debug, Clone)]
pub struct FileError {
pub path: String,
pub code: String,
pub message: String,
pub transient: bool,
}