use crate::language::{
KERNEL_LANGUAGE, LanguageVocabulary, identifiers, informative_tokens, surface_tokens,
};
use super::search_summary_fault::SearchSummaryFault;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SearchSummary {
summary: String,
}
impl SearchSummary {
pub const METADATA_KEY: &'static str = "summary_en";
pub const MINIMUM_INFORMATIVE_TERMS: usize = 2;
pub fn lint(text: &str, summary: &str) -> Result<Self, Vec<SearchSummaryFault>> {
let mut faults = Vec::new();
if let Some(language) = LanguageVocabulary::shipped().leans_in(summary)
&& language != KERNEL_LANGUAGE
{
faults.push(SearchSummaryFault::NotEnglish {
read: language.to_string(),
});
}
let summary_terms = informative_tokens(summary).collect::<Vec<_>>();
if summary_terms.len() < Self::MINIMUM_INFORMATIVE_TERMS {
faults.push(SearchSummaryFault::Thin {
informative_terms: summary_terms.len(),
});
} else if summary_terms == informative_tokens(text).collect::<Vec<_>>() {
faults.push(SearchSummaryFault::RepeatsText);
}
let carried = surface_tokens(summary);
let dropped = identifiers(text)
.into_iter()
.filter(|identifier| !carried.contains(identifier))
.collect::<Vec<_>>();
if !dropped.is_empty() {
faults.push(SearchSummaryFault::DropsIdentifiers(dropped));
}
if faults.is_empty() {
Ok(Self {
summary: summary.to_string(),
})
} else {
Err(faults)
}
}
pub fn as_str(&self) -> &str {
&self.summary
}
}
#[cfg(test)]
mod tests {
use super::*;
const SPANISH_TEXT: &str =
"El despliegue de v0.7.0 se retrasó porque los auditores no habían firmado (#469).";
fn faults(text: &str, summary: &str) -> Vec<SearchSummaryFault> {
SearchSummary::lint(text, summary).expect_err("the summary should be refused")
}
#[test]
fn an_english_sentence_that_keeps_the_identifiers_carries() {
let summary = SearchSummary::lint(
SPANISH_TEXT,
"The v0.7.0 launch was postponed because the auditors had not signed off (#469).",
)
.expect("a faithful English rendering carries");
assert!(summary.as_str().starts_with("The v0.7.0 launch"));
}
#[test]
fn a_summary_in_the_texts_own_language_is_refused() {
let faults = faults(
SPANISH_TEXT,
"El lanzamiento de v0.7.0 se pospuso porque los auditores no firmaron (#469).",
);
assert_eq!(
faults,
[SearchSummaryFault::NotEnglish {
read: "spanish".to_string()
}]
);
assert_eq!(faults[0].to_string(), "leans to spanish, not to English");
}
#[test]
fn a_list_of_english_keywords_carries() {
SearchSummary::lint(SPANISH_TEXT, "launch v0.7.0 postponed auditors #469")
.expect("keywords in the kernel's language are a search surface");
}
#[test]
fn a_summary_that_drops_an_identifier_is_refused_and_names_it() {
let faults = faults(
SPANISH_TEXT,
"The launch was postponed because the auditors had not signed off.",
);
assert_eq!(
faults,
[SearchSummaryFault::DropsIdentifiers(vec![
"#469".to_string(),
"v0.7.0".to_string()
])]
);
assert_eq!(
faults[0].to_string(),
"drops identifiers the text carries: #469, v0.7.0"
);
}
#[test]
fn identifiers_are_matched_folded_and_without_clause_punctuation() {
SearchSummary::lint(
"Se adoptó Valkey 7.2 en la rama feat/valkey-store; ver ADR-018 y kmp-mcp.",
"Valkey 7.2 was adopted on the feat/valkey-store branch; see ADR-018 and KMP-MCP.",
)
.expect("case and edge punctuation do not decide fidelity");
}
#[test]
fn an_abbreviation_is_not_an_identifier() {
SearchSummary::lint(
"Los adaptadores (p.ej. el de Valkey) se registran al arrancar.",
"The adapters, e.g. the Valkey one, are registered at start-up.",
)
.expect("`p.ej.` is rendered, not copied");
}
#[test]
fn a_copy_of_the_text_adds_nothing_and_is_refused() {
let text = "The reserve valve failed during the night shift.";
assert_eq!(
faults(text, "The reserve valve failed during the night shift."),
[SearchSummaryFault::RepeatsText]
);
}
#[test]
fn a_thin_summary_is_refused() {
let faults = faults("La válvula de reserva se congeló.", "The valve.");
assert!(
faults.contains(&SearchSummaryFault::Thin {
informative_terms: 1
}),
"{faults:?}"
);
}
#[test]
fn every_fault_is_reported_at_once() {
let faults = faults(SPANISH_TEXT, "Se pospuso.");
assert_eq!(faults.len(), 3, "{faults:?}");
assert_eq!(
SearchSummaryFault::describe(&faults),
"leans to spanish, not to English; \
carries 1 informative word, at least 2 are needed; \
drops identifiers the text carries: #469, v0.7.0"
);
}
}