use super::{Corpus, CorpusVerificationError};
use std::path::Path;
pub fn load_and_verify_corpus() -> Result<Corpus, CorpusVerificationError> {
let corpus = super::data::build_default_corpus();
corpus.verify_category_coverage()?;
corpus.verify_split_integrity()?;
Ok(corpus)
}
pub fn load_corpus_from_file(path: &Path) -> Result<Corpus, LoadError> {
let content = std::fs::read_to_string(path)
.map_err(|e| LoadError::Io(format!("{}: {}", path.display(), e)))?;
let corpus: Corpus = serde_json::from_str(&content)
.map_err(|e| LoadError::Parse(format!("{}: {}", path.display(), e)))?;
Ok(corpus)
}
pub fn load_and_verify_corpus_from_file(path: &Path) -> Result<Corpus, CorpusVerificationError> {
let corpus = load_corpus_from_file(path).map_err(|e| {
CorpusVerificationError::MissingCategories(vec![format!("Load error: {e}")])
})?;
corpus.verify_category_coverage()?;
corpus.verify_split_integrity()?;
Ok(corpus)
}
pub fn save_corpus_to_file(corpus: &Corpus, path: &Path) -> Result<(), SaveError> {
let json =
serde_json::to_string_pretty(corpus).map_err(|e| SaveError::Serialize(e.to_string()))?;
std::fs::write(path, json).map_err(|e| SaveError::Io(format!("{}", e)))?;
Ok(())
}
pub fn corpus_languages(corpus: &Corpus) -> Vec<super::Language> {
use std::collections::HashSet;
let mut langs: HashSet<super::Language> = HashSet::new();
for case in &corpus.cases {
for lang in &case.languages {
langs.insert(*lang);
}
}
let mut result: Vec<_> = langs.into_iter().collect();
result.sort_by_key(|l| l.as_str());
result
}
pub fn category_counts(corpus: &Corpus) -> std::collections::HashMap<super::EvalCategory, usize> {
let mut counts = std::collections::HashMap::new();
for case in &corpus.cases {
*counts.entry(case.category).or_insert(0) += 1;
}
counts
}
#[derive(Debug, Clone)]
pub enum LoadError {
Io(String),
Parse(String),
}
impl std::fmt::Display for LoadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Io(msg) => write!(f, "IO error: {msg}"),
Self::Parse(msg) => write!(f, "Parse error: {msg}"),
}
}
}
impl std::error::Error for LoadError {}
#[derive(Debug, Clone)]
pub enum SaveError {
Serialize(String),
Io(String),
}
impl std::fmt::Display for SaveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Serialize(msg) => write!(f, "Serialize error: {msg}"),
Self::Io(msg) => write!(f, "IO error: {msg}"),
}
}
}
impl std::error::Error for SaveError {}
#[cfg(test)]
mod tests {
use super::super::{EvalCategory, Language, Split};
use super::*;
#[test]
fn test_load_and_verify_default_corpus() {
let corpus = load_and_verify_corpus().expect("default corpus should verify");
assert!(corpus.len() >= 28); assert!(corpus.eval_len() > 0);
assert!(corpus.train_len() > 0);
}
#[test]
fn test_default_corpus_covers_all_categories() {
let corpus = load_and_verify_corpus().expect("corpus should verify");
for cat in EvalCategory::all() {
let count = corpus.cases_for_category(cat).count();
assert!(
count >= 2,
"Category '{}' has {} cases, need >= 2",
cat.as_str(),
count
);
}
}
#[test]
fn test_default_corpus_has_eval_cases_for_every_category() {
let corpus = load_and_verify_corpus().expect("corpus should verify");
for cat in EvalCategory::all() {
let eval_count = corpus
.cases_for_category(cat)
.filter(|c| c.split == Split::Eval)
.count();
assert!(
eval_count >= 1,
"Category '{}' has {} eval cases, need >= 1",
cat.as_str(),
eval_count
);
}
}
#[test]
fn test_corpus_languages_covers_rust() {
let corpus = load_and_verify_corpus().expect("corpus should verify");
let langs = corpus_languages(&corpus);
assert!(
langs.contains(&Language::Rust),
"Corpus should include Rust"
);
}
#[test]
fn test_category_counts_all_present() {
let corpus = load_and_verify_corpus().expect("corpus should verify");
let counts = category_counts(&corpus);
for cat in EvalCategory::all() {
assert!(
counts.contains_key(&cat),
"Category '{}' missing from counts",
cat.as_str()
);
}
}
#[test]
fn test_corpus_roundtrip_json() {
let corpus = load_and_verify_corpus().expect("corpus should verify");
let json = serde_json::to_string(&corpus).expect("serialize");
let back: Corpus = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.len(), corpus.len());
}
#[test]
fn test_save_and_load_corpus_file() {
let corpus = load_and_verify_corpus().expect("corpus should verify");
let tmp = tempfile::NamedTempFile::new().expect("create temp file");
let path = tmp.path();
save_corpus_to_file(&corpus, path).expect("save");
let loaded = load_corpus_from_file(path).expect("load");
assert_eq!(loaded.len(), corpus.len());
}
#[test]
fn test_multi_language_coverage() {
let corpus = load_and_verify_corpus().expect("corpus should verify");
let multi_cases: Vec<_> = corpus
.cases_for_category(EvalCategory::MultiLanguage)
.collect();
assert!(multi_cases.len() >= 2);
let langs: std::collections::HashSet<_> = multi_cases
.iter()
.flat_map(|c| c.languages.iter().copied())
.collect();
assert!(
langs.len() >= 3,
"Multi-language category covers {:?} languages, expected >= 3",
langs
);
}
#[test]
fn test_hard_negatives_have_hard_negative_labels() {
let corpus = load_and_verify_corpus().expect("corpus should verify");
let hn_cases: Vec<_> = corpus
.cases_for_category(EvalCategory::HardNegatives)
.collect();
for case in &hn_cases {
assert!(
!case.hard_negatives.is_empty(),
"Hard negative case '{}' has no hard_negatives labels",
case.id
);
}
}
}