Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gtars-bbcache/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repository = "https://github.com/databio/gtars"

[dependencies]
biocrs = "0.1.0"
reqwest = { version = "0.12.15", features = ["blocking"] }
ureq = "3.1.4"
shellexpand = "3"
tabled = "0.20.0"
dirs = "6.0.0"
Expand Down
9 changes: 7 additions & 2 deletions gtars-bbcache/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use anyhow::{Context, Ok, Result, anyhow};
use biocrs::biocache::BioCache;
use biocrs::models::{NewResource, Resource};

use reqwest::blocking::get;
use ureq::get;
use std::fs::{File, create_dir_all, read_dir, remove_dir, remove_file};
use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -367,7 +367,12 @@ impl BBClient {
fn download_bedset_data(&self, bedset_id: &str) -> Result<Vec<String>> {
let bedset_url = format!("{}/v1/bedset/{}/bedfiles", self.bedbase_api, bedset_id);

let response = get(&bedset_url)?.text()?;
let response = get(&bedset_url)
.call()
.map_err(|e| anyhow!("Failed to GET {}: {}", bedset_url, e))?
.body_mut()
.read_to_string()
Comment on lines +373 to +374
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .body_mut().read_to_string() pattern is incorrect for ureq. The read_to_string() method from std::io::Read requires a mutable String buffer as an argument (e.g., read_to_string(&mut buffer)), but this code doesn't provide one.

For ureq 3.x, the correct pattern is to use .into_string() which consumes the response and returns Result<String, Error>:

let response = get(&bedset_url)
    .call()
    .map_err(|e| anyhow!("Failed to GET {}: {}", bedset_url, e))?
    .into_string()
    .map_err(|e| anyhow!("Failed to read response body for {}: {}", bedset_url, e))?;

This is more idiomatic, handles errors properly, and avoids the need for the intermediate .body_mut() call.

Suggested change
.body_mut()
.read_to_string()
.into_string()

Copilot uses AI. Check for mistakes.
.map_err(|e| anyhow!("Failed to read response body for {}: {}", bedset_url, e))?;

let json: serde_json::Value = serde_json::from_str(&response)?;

Expand Down
4 changes: 2 additions & 2 deletions gtars-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repository = "https://github.com/databio/gtars"
[dependencies]
md-5 = "0.10.6"
num-traits = "0.2.19"
reqwest = { version = "0.12.23", features = ["blocking"], optional=true }
ureq = {version = "3.1.4", optional=true}
tokio = "1.47.1"
bigtools = { version = "0.5.6", optional = true }

Expand All @@ -24,4 +24,4 @@ tempfile = "3.21.0"
[features]
default = []
bigbed = ["dep:bigtools"]
http = ["dep:reqwest"]
http = ["dep:ureq"]
7 changes: 4 additions & 3 deletions gtars-core/src/models/region_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,14 +567,15 @@ mod tests {
}

#[rstest]
#[ignore = "Failing but low priority for now"]
fn test_open_from_url() {
let file_path = String::from("https://github.com/databio/gtars/raw/refs/heads/master/gtars/tests/data/regionset/dummy.narrowPeak.bed.gz");
//let file_path = String::from("https://github.com/databio/gtars/raw/refs/heads/master/gtars/tests/data/regionset/dummy.narrowPeak.bed.gz");
let file_path = String::from("https://github.com/databio/gtars/raw/refs/heads/dev/tests/data/regionset/dummy.narrowPeak.bed.gz");
assert!(RegionSet::try_from(file_path).is_ok());
// let res = RegionSet::try_from(file_path);
// assert!(res.is_ok(), "try_from failed:\n{:#}", res.unwrap_err());
}

#[rstest]
#[ignore = "Failing but low priority"]
fn test_open_from_bedbase() {
let bbid = String::from("6b2e163a1d4319d99bd465c6c78a9741");
let region_set = RegionSet::try_from(bbid);
Expand Down
49 changes: 30 additions & 19 deletions gtars-core/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use std::str::FromStr;

use anyhow::{Context, Result};
use flate2::read::{GzDecoder, MultiGzDecoder};
#[cfg(feature = "http")]
use reqwest::blocking::Client;
use std::error::Error;
#[cfg(feature = "http")]
use ureq::{get, Error as UreqError};

use crate::models::region::Region;

Expand Down Expand Up @@ -133,31 +133,42 @@ pub fn get_dynamic_reader(path: &Path) -> Result<BufReader<Box<dyn Read>>> {
pub fn get_dynamic_reader_from_url(
url: &Path,
) -> Result<BufReader<Box<dyn std::io::Read>>, Box<dyn Error>> {
// Create an HTTP client and fetch the content
let mut url: String = url.to_str().unwrap().to_string();

let is_ftp: bool = url.starts_with("ftp");
let mut url_str = url
.to_str()
.ok_or_else(|| "URL path is not valid UTF-8")?
.to_string();

let is_ftp = url_str.starts_with("ftp://");
if is_ftp {
println!("ftp is not fully implemented. Bugs could appear");
url = url.replacen("ftp://", "http://", 1);
url_str = url_str.replacen("ftp://", "http://", 1);
}

let response = Client::new()
.get(&url)
.send()
.with_context(|| format!("Failed to fetch content from URL: {}", &url))?
.error_for_status()?
.bytes()?;
let is_gzipped = url_str.ends_with(".gz");

// Convert the response into a cursor for reading
let cursor = Cursor::new(response);
// Perform request
let response = match get(&url_str).call() {
Ok(resp) => resp,
Err(UreqError::StatusCode(code)) => {
return Err(format!("HTTP status {} when fetching {}", code, url_str).into())
}
Err(e) => return Err(format!("Request error when fetching {}: {}", url_str, e).into()),
};

// Read full body into memory (same behavior as reqwest .bytes()?)
let mut bytes = Vec::new();
response
.into_body()
.into_reader()
.read_to_end(&mut bytes)
.map_err(|e| format!("Failed reading response body from {}: {}", url_str, e))?;

let is_gzipped = url.ends_with(".gz");
let cursor = Cursor::new(bytes);

let reader: Box<dyn std::io::Read> = match is_gzipped {
true => Box::new(GzDecoder::new(cursor)),
false => Box::new(cursor),
let reader: Box<dyn std::io::Read> = if is_gzipped {
Box::new(GzDecoder::new(cursor))
} else {
Box::new(cursor)
};

Ok(BufReader::new(reader))
Expand Down