use crate::error::ScrapsResult;
use crate::usecase::build::model::backlinks_map::BacklinksMap;
use crate::usecase::scrap::lookup_links::usecase::LinkRefKind;
use crate::usecase::scrap::scrap_ref::{ScrapRef, find_scrap};
use scraps_libs::model::key::ScrapKey;
use scraps_libs::model::scrap::Scrap;
#[derive(Debug, Clone, PartialEq)]
pub struct LookupScrapBacklinksResult {
pub scrap: ScrapRef,
pub md_text: String,
pub kinds: Vec<LinkRefKind>,
}
pub struct LookupScrapBacklinksUsecase;
impl LookupScrapBacklinksUsecase {
pub fn new() -> LookupScrapBacklinksUsecase {
LookupScrapBacklinksUsecase
}
pub fn execute(
&self,
scraps: &[Scrap],
key: &ScrapKey,
) -> ScrapsResult<Vec<LookupScrapBacklinksResult>> {
find_scrap(scraps, key)?;
let backlinks_map = BacklinksMap::new(scraps);
let linking_scraps = backlinks_map.get(key);
let results: Vec<LookupScrapBacklinksResult> = linking_scraps
.iter()
.map(|linking_scrap| LookupScrapBacklinksResult {
scrap: (*linking_scrap).into(),
md_text: linking_scrap.md_text().to_string(),
kinds: link_kinds(linking_scrap, key),
})
.collect();
Ok(results)
}
}
fn link_kinds(linking: &Scrap, key: &ScrapKey) -> Vec<LinkRefKind> {
let in_body = linking
.link_occurrences()
.any(|link| link.target.as_ref() == Some(key));
let as_source = linking
.source_links()
.iter()
.any(|link| link.target.as_ref() == Some(key));
[
(in_body, LinkRefKind::Link),
(as_source, LinkRefKind::Source),
]
.into_iter()
.filter_map(|(found, kind)| found.then_some(kind))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lookup_scrap_backlinks_success() {
let scraps = vec![
Scrap::new(
"scrap1",
&None,
"# Scrap 1\n\nThis links to [[target_scrap]].",
),
Scrap::new(
"scrap2",
&None,
"# Scrap 2\n\nThis also links to [[target_scrap]].",
),
Scrap::new(
"target_scrap",
&None,
"# Target Scrap\n\nContent of target scrap.",
),
];
let usecase = LookupScrapBacklinksUsecase::new();
let results = usecase
.execute(&scraps, &ScrapKey::from_path_str("target_scrap"))
.expect("Should succeed");
assert_eq!(results.len(), 2);
let titles: Vec<String> = results.iter().map(|r| r.scrap.title.clone()).collect();
assert!(titles.contains(&"scrap1".to_string()));
assert!(titles.contains(&"scrap2".to_string()));
}
#[test]
fn test_lookup_scrap_backlinks_with_context() {
let scraps = vec![
Scrap::new(
"scrap1",
&None,
"# Scrap 1\n\nThis links to [[Context/target_scrap]].",
),
Scrap::new(
"target_scrap",
&Some("Context".into()),
"# Target Scrap\n\nContent of target scrap.",
),
];
let usecase = LookupScrapBacklinksUsecase::new();
let results = usecase
.execute(&scraps, &ScrapKey::from_path_str("Context/target_scrap"))
.expect("Should succeed");
assert_eq!(results.len(), 1);
assert_eq!(results[0].scrap.title, "scrap1");
}
#[test]
fn test_lookup_scrap_backlinks_not_found() {
let scraps = vec![Scrap::new("scrap1", &None, "# Scrap 1\n\nContent.")];
let usecase = LookupScrapBacklinksUsecase::new();
let result = usecase.execute(&scraps, &ScrapKey::from_path_str("Nonexistent Scrap"));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Scrap not found"));
}
#[test]
fn test_lookup_scrap_backlinks_no_backlinks() {
let scraps = vec![Scrap::new(
"target_scrap",
&None,
"# Target Scrap\n\nThis scrap has no backlinks.",
)];
let usecase = LookupScrapBacklinksUsecase::new();
let results = usecase
.execute(&scraps, &ScrapKey::from_path_str("target_scrap"))
.expect("Should succeed");
assert_eq!(results.len(), 0);
}
#[test]
fn test_lookup_scrap_backlinks_tells_how_each_scrap_links_here() {
let scraps = vec![
Scrap::new("body", &None, "[[Intro]]"),
Scrap::new(
"source",
&None,
"---\nsources:\n - resource: /Intro.md\n---\n",
),
Scrap::new(
"both",
&None,
"---\nsources:\n - resource: /Intro.md\n---\n\n[[Intro]]\n",
),
Scrap::new("Intro", &None, ""),
];
let results = LookupScrapBacklinksUsecase::new()
.execute(&scraps, &ScrapKey::from_path_str("Intro"))
.unwrap();
let kinds: Vec<(String, Vec<LinkRefKind>)> = results
.into_iter()
.map(|r| (r.scrap.key.to_string(), r.kinds))
.collect();
assert_eq!(
kinds,
vec![
("body".to_string(), vec![LinkRefKind::Link]),
("source".to_string(), vec![LinkRefKind::Source]),
(
"both".to_string(),
vec![LinkRefKind::Link, LinkRefKind::Source]
),
]
);
}
}