use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::core::error::{Error, Result};
pub const DOC_PAIRS_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum PairSource {
Mention,
Mirror,
Llm,
Manual,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct DocPair {
pub doc: String,
pub srcs: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub confidence: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<PairSource>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct DocPairsFile {
pub version: u32,
#[serde(default)]
pub pairs: Vec<DocPair>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DocPairsWarning {
pub pair_index: usize,
pub missing_path: PathBuf,
}
impl DocPairsFile {
pub fn read(project: &Path, pairs_path: &str) -> Result<Option<Self>> {
let abs = project.join(pairs_path);
let raw = match std::fs::read_to_string(&abs) {
Ok(s) => s,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(source) => return Err(Error::Io { path: abs, source }),
};
let parsed: Self = serde_json::from_str(&raw).map_err(|source| Error::CacheParse {
path: abs.clone(),
source,
})?;
if parsed.version != DOC_PAIRS_VERSION {
return Ok(None);
}
Ok(Some(parsed))
}
#[must_use]
pub fn integrity_check(&self, project: &Path) -> Vec<DocPairsWarning> {
let mut warnings = Vec::new();
for (idx, pair) in self.pairs.iter().enumerate() {
let doc_path = project.join(&pair.doc);
if !doc_path.exists() {
warnings.push(DocPairsWarning {
pair_index: idx,
missing_path: PathBuf::from(&pair.doc),
});
}
for src in &pair.srcs {
let src_path = project.join(src);
if !src_path.exists() {
warnings.push(DocPairsWarning {
pair_index: idx,
missing_path: PathBuf::from(src),
});
}
}
}
warnings
}
#[must_use]
pub fn live_pairs(&self, project: &Path) -> Vec<&DocPair> {
self.pairs
.iter()
.filter(|p| {
project.join(&p.doc).exists() && p.srcs.iter().all(|s| project.join(s).exists())
})
.collect()
}
}