Skip to main content

gtars_bbcache/
lib.rs

1//! # gtars-bbcache
2//!
3//! A Rust implementation of bbcache: a caching system for BED files from [BEDbase.org](https://bedbase.org).
4//!
5//! ## Overview
6//!
7//! `gtars-bbcache` provides an efficient local caching layer for BED files and BED sets retrieved from
8//! the BEDbase API. It handles downloading, storage, and retrieval of genomic region data.
9//!
10//! ## Features
11//!
12//! - **Smart Caching**: Automatically downloads and caches BED files from BEDbase API on first access
13//! - **BEDset Support**: Manages collections of related BED files as coherent sets
14//! - **Local File Import**: Add local BED files to the cache for unified access
15//! - **Efficient Storage**: Organizes cached files in a hierarchical directory structure
16//! - **SQLite Tracking**: Uses biocache for fast lookups and resource management
17//! - **Configurable**: Customize cache location and API endpoints via environment variables
18//!
19//! ## Quick Start
20//!
21//! ```rust,no_run
22//! use gtars_bbcache::client::BBClient;
23//! use std::path::PathBuf;
24//!
25//! # fn main() -> anyhow::Result<()> {
26//! // Create a client with default settings
27//! let mut client = BBClient::builder().finish()?;
28//!
29//! // Load a BED file from BEDbase (downloads and caches if not present)
30//! let region_set = client.load_bed("6b2e163a1d4319d99bd465c6c78a9741")?;
31//!
32//! // Add a local BED file to the cache
33//! let bed_id = client.add_local_bed_to_cache(
34//!     PathBuf::from("path/to/file.bed.gz"),
35//!     None
36//! )?;
37//!
38//! // Check if a file exists in cache
39//! let cached_path = client.seek(&bed_id)?;
40//! # Ok(())
41//! # }
42//! ```
43//!
44//! ## Configuration
45//!
46//! The cache behavior can be configured through environment variables:
47//!
48//! - `BBCLIENT_CACHE`: Custom cache directory (default: `~/.bbcache/`)
49//! - `BEDBASE_API`: Custom BEDbase API endpoint (default: `https://api.bedbase.org`)
50//!
51//! Or programmatically via the builder:
52//!
53//! ```rust,no_run
54//! use gtars_bbcache::client::BBClient;
55//! use std::path::PathBuf;
56//!
57//! # fn main() -> anyhow::Result<()> {
58//! let client = BBClient::builder()
59//!     .with_cache_folder(PathBuf::from("/custom/cache/path"))
60//!     .with_bedbase_api("https://api.bedbase.org".to_string())
61//!     .finish()?;
62//! # Ok(())
63//! # }
64//! ```
65
66pub mod client;
67pub mod consts;
68pub mod utils;
69
70#[cfg(test)]
71mod tests {
72    use super::client::BBClient;
73    use rstest::{fixture, rstest};
74    use std::fs::read_dir;
75    use std::path::PathBuf;
76
77    #[fixture]
78    fn path_to_bed_gz_from_bb() -> PathBuf {
79        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
80            .parent()
81            .unwrap()
82            .join("tests/data/6b2e163a1d4319d99bd465c6c78a9741.bed.gz")
83    }
84
85    #[fixture]
86    fn bbid() -> PathBuf {
87        "6b2e163a1d4319d99bd465c6c78a9741".into()
88    }
89
90    #[fixture]
91    fn path_to_bedset() -> PathBuf {
92        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
93            .parent()
94            .unwrap()
95            .join("tests/data/bedset")
96    }
97
98    #[rstest]
99    fn test_bbcache_local(
100        path_to_bed_gz_from_bb: PathBuf,
101        bbid: PathBuf,
102        path_to_bedset: PathBuf,
103    ) -> Result<(), Box<dyn std::error::Error + 'static>> {
104        fn cleaned_subfolders(subfolder: PathBuf) {
105            let subdirs: Vec<_> = read_dir(&subfolder)
106                .unwrap_or_else(|e| {
107                    panic!("Failed to read directory {}: {}", subfolder.display(), e)
108                })
109                .filter_map(Result::ok)
110                .filter(|entry| entry.path().is_dir())
111                .collect();
112
113            // Assert no subdirectories exist
114            assert!(
115                subdirs.is_empty(),
116                "Subfolders found in {}: {:?}",
117                subfolder.display(),
118                subdirs.iter().map(|e| e.path()).collect::<Vec<_>>()
119            );
120        }
121        let tempdir = tempfile::tempdir()?;
122        let cache_folder = PathBuf::from(tempdir.path());
123
124        let mut bbc = BBClient::builder()
125            .with_cache_folder(cache_folder.clone())
126            .finish()?;
127
128        let bed_id = bbc
129            .add_local_bed_to_cache(path_to_bed_gz_from_bb, Some(false))
130            .unwrap();
131        assert_eq!(&bed_id, &bbid.to_string_lossy());
132
133        let bedset_id = bbc.add_local_folder_as_bedset(path_to_bedset).unwrap();
134        assert!(bbc.seek(&bedset_id).is_ok());
135
136        bbc.remove(&bedset_id)
137            .expect("Failed to remove bedset file and its bed files");
138        let bedset_subfolder = cache_folder.join("bedsets");
139        cleaned_subfolders(bedset_subfolder);
140
141        bbc.remove(&bbid.to_string_lossy())
142            .expect("Failed to remove cached bed file");
143        let bedfile_subfolder = cache_folder.join("bedfiles");
144        cleaned_subfolders(bedfile_subfolder);
145        Ok(())
146    }
147}