use std::io::Read as _;
use anyhow::Context as _;
pub(crate) fn read_optional(
inline: Option<String>,
file: Option<&str>,
) -> anyhow::Result<Option<String>> {
if let Some(query) = inline {
return Ok(Some(query));
}
let Some(path) = file else {
return Ok(None);
};
let text = if path == "-" {
let mut bytes = Vec::new();
std::io::stdin()
.read_to_end(&mut bytes)
.context("cannot read the query from standard input (--query-file -)")?;
String::from_utf8(bytes)
.map_err(|_| anyhow::anyhow!("the query on standard input is not UTF-8"))?
} else {
let bytes =
std::fs::read(path).with_context(|| format!("cannot read --query-file {path}"))?;
String::from_utf8(bytes)
.map_err(|_| anyhow::anyhow!("--query-file {path} is not UTF-8 text"))?
};
let text = match text.strip_prefix('\u{feff}') {
Some(rest) => rest.to_string(),
None => text,
};
if text.trim().is_empty() {
let source = if path == "-" {
"standard input".to_string()
} else {
format!("--query-file {path}")
};
anyhow::bail!("the query from {source} is empty");
}
Ok(Some(text))
}
pub(crate) fn read(inline: Option<String>, file: Option<&str>) -> anyhow::Result<String> {
read_optional(inline, file)?.ok_or_else(|| {
anyhow::anyhow!(
"no query: pass it as an argument or with --query-file <path> (- for stdin)"
)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn inline_wins_and_a_file_is_read_whole() {
assert_eq!(read(Some("ASK {}".into()), None).unwrap(), "ASK {}");
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("q.rq");
let big = format!(
"SELECT * WHERE {{ VALUES ?x {{ {} }} }}",
(0..20_000)
.map(|i| format!("<http://example.test/{i}>"))
.collect::<Vec<_>>()
.join(" ")
);
assert!(big.len() > 128 * 1024);
std::fs::write(&path, &big).unwrap();
assert_eq!(read(None, path.to_str()).unwrap(), big);
}
#[test]
fn bom_empty_missing_and_non_utf8_files() {
let dir = tempfile::tempdir().unwrap();
let bom = dir.path().join("bom.rq");
std::fs::write(&bom, "\u{feff}ASK {}").unwrap();
assert_eq!(read(None, bom.to_str()).unwrap(), "ASK {}");
let empty = dir.path().join("empty.rq");
std::fs::write(&empty, " \n").unwrap();
let err = read(None, empty.to_str()).unwrap_err().to_string();
assert!(err.contains("is empty"), "{err}");
let missing = dir.path().join("missing.rq");
let err = format!("{:#}", read(None, missing.to_str()).unwrap_err());
assert!(err.contains("cannot read --query-file"), "{err}");
let latin1 = dir.path().join("latin1.rq");
std::fs::write(&latin1, b"ASK { ?s ?p \"caf\xe9\" }").unwrap();
let err = read(None, latin1.to_str()).unwrap_err().to_string();
assert!(err.contains("not UTF-8"), "{err}");
assert!(read_optional(None, None).unwrap().is_none());
assert!(read(None, None).is_err());
}
}