Skip to content

Commit c6903c7

Browse files
committed
- New public rdf2hdt::Error enum with Io, Hdt, Parse variants. hdt::hdt::Error no longer appears in the public signature — it's wrapped in Error::Hdt.
- build_hdt signature: fn build_hdt<P: AsRef<Path>, Q: AsRef<Path>>(inputs: &[P], dest: Q) -> Result<hdt::Hdt, Error>. Callers can pass &Vec<String>, &[PathBuf], &[&str], etc.
1 parent 9660ea9 commit c6903c7

6 files changed

Lines changed: 101 additions & 65 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rdf2hdt"
3-
version = "0.3.0"
3+
version = "0.4.0"
44
authors = ["Greg Hanson <g.isaac.hanson@gmail.com>"]
55
edition = "2024"
66
license = "BSD-3-Clause"

src/builder.rs

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,72 @@
44
use crate::rdf_reader::convert_to_nt;
55
use log::{debug, error};
66
use std::{
7+
fmt,
78
fs::OpenOptions,
89
io::{self, BufWriter, Write},
910
path::{Path, PathBuf},
1011
};
1112

12-
pub fn build_hdt(file_paths: Vec<String>, dest_file: &str) -> Result<hdt::Hdt, hdt::hdt::Error> {
13-
if file_paths.is_empty() {
13+
#[derive(Debug)]
14+
pub enum Error {
15+
Io(io::Error),
16+
Hdt(hdt::hdt::Error),
17+
Parse(Box<dyn std::error::Error + Send + Sync>),
18+
}
19+
20+
impl fmt::Display for Error {
21+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22+
match self {
23+
Error::Io(e) => write!(f, "I/O error: {e}"),
24+
Error::Hdt(e) => write!(f, "HDT error: {e}"),
25+
Error::Parse(e) => write!(f, "parse error: {e}"),
26+
}
27+
}
28+
}
29+
30+
impl std::error::Error for Error {
31+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
32+
match self {
33+
Error::Io(e) => Some(e),
34+
Error::Hdt(e) => Some(e),
35+
Error::Parse(e) => Some(e.as_ref()),
36+
}
37+
}
38+
}
39+
40+
impl From<io::Error> for Error {
41+
fn from(e: io::Error) -> Self {
42+
Error::Io(e)
43+
}
44+
}
45+
46+
impl From<hdt::hdt::Error> for Error {
47+
fn from(e: hdt::hdt::Error) -> Self {
48+
Error::Hdt(e)
49+
}
50+
}
51+
52+
pub fn build_hdt<P: AsRef<Path>, Q: AsRef<Path>>(inputs: &[P], dest: Q) -> Result<hdt::Hdt, Error> {
53+
if inputs.is_empty() {
1454
error!("no files provided");
1555
return Err(
16-
io::Error::new(io::ErrorKind::InvalidData, "no files provided to convert").into(),
56+
io::Error::new(io::ErrorKind::InvalidInput, "no files provided to convert").into(),
1757
);
1858
}
1959

2060
let timer = std::time::Instant::now();
21-
let is_nt = file_paths.len() == 1
22-
&& Path::new(&file_paths[0])
61+
let first = inputs[0].as_ref();
62+
let is_nt = inputs.len() == 1
63+
&& first
2364
.extension()
2465
.and_then(|e| e.to_str())
2566
.is_some_and(|e| e.eq_ignore_ascii_case("nt"));
2667

2768
let (nt_path, _tmp_guard): (PathBuf, Option<tempfile::NamedTempFile>) = if is_nt {
28-
(PathBuf::from(&file_paths[0]), None)
69+
(first.to_path_buf(), None)
2970
} else {
3071
let tmp = tempfile::Builder::new().suffix(".nt").tempfile()?;
31-
convert_to_nt(file_paths, tmp.reopen()?).map_err(|e| io::Error::other(e.to_string()))?;
72+
convert_to_nt(inputs, tmp.reopen()?)?;
3273
(tmp.path().to_path_buf(), Some(tmp))
3374
};
3475

@@ -40,7 +81,7 @@ pub fn build_hdt(file_paths: Vec<String>, dest_file: &str) -> Result<hdt::Hdt, h
4081
.write(true)
4182
.create(true)
4283
.truncate(true)
43-
.open(dest_file)?;
84+
.open(dest.as_ref())?;
4485
let mut writer = BufWriter::new(out_file);
4586
converted_hdt.write(&mut writer)?;
4687
writer.flush()?;
@@ -55,7 +96,7 @@ mod tests {
5596
use super::*;
5697
use walkdir::WalkDir;
5798

58-
fn run_sparql_suite(suite: &str) -> hdt::hdt::Result<()> {
99+
fn run_sparql_suite(suite: &str) -> Result<(), Error> {
59100
let suite_dir = format!("tests/resources/rdf-tests/sparql/{suite}");
60101
assert!(
61102
std::path::Path::new(&suite_dir).exists(),
@@ -86,12 +127,9 @@ mod tests {
86127
let stem = p.file_stem().and_then(|s| s.to_str()).unwrap_or("out");
87128
let out = tmp
88129
.path()
89-
.join(format!("{}_{}.hdt", parent_name.unwrap_or("root"), stem,));
90-
let out_str = out
91-
.to_str()
92-
.expect("tempdir path should be valid UTF-8 on test platforms");
130+
.join(format!("{}_{}.hdt", parent_name.unwrap_or("root"), stem));
93131

94-
if let Err(e) = build_hdt(vec![f.to_string()], out_str) {
132+
if let Err(e) = build_hdt(std::slice::from_ref(f), &out) {
95133
failures.push(format!("{f}: {e}"));
96134
}
97135
}
@@ -106,17 +144,17 @@ mod tests {
106144
}
107145

108146
#[test]
109-
fn sparql10_tests() -> hdt::hdt::Result<()> {
147+
fn sparql10_tests() -> Result<(), Error> {
110148
run_sparql_suite("sparql10")
111149
}
112150

113151
#[test]
114-
fn sparql11_tests() -> hdt::hdt::Result<()> {
152+
fn sparql11_tests() -> Result<(), Error> {
115153
run_sparql_suite("sparql11")
116154
}
117155

118156
#[test]
119-
fn sparql12_tests() -> hdt::hdt::Result<()> {
157+
fn sparql12_tests() -> Result<(), Error> {
120158
run_sparql_suite("sparql12")
121159
}
122160

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33

44
pub mod builder;
55
pub(crate) mod rdf_reader;
6+
7+
pub use builder::{Error, build_hdt};

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ fn main() -> ExitCode {
7676
.init();
7777

7878
match &cli.command {
79-
Some(Commands::Convert { input, output }) => match build_hdt(input.clone(), output) {
79+
Some(Commands::Convert { input, output }) => match build_hdt(input, output) {
8080
Ok(_) => ExitCode::SUCCESS,
8181
Err(e) => {
8282
eprintln!("Error: {e}");

src/rdf_reader.rs

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2025, Decisym, LLC
22
// Licensed under the BSD 3-Clause License (see LICENSE file in the project root).
33

4+
use crate::builder::Error;
45
use log::{debug, error, warn};
56
use oxrdfio::RdfSerializer;
67
use oxrdfio::{
@@ -9,78 +10,73 @@ use oxrdfio::{
910
};
1011
use std::io::Write;
1112
use std::{
12-
error::Error,
13-
io::{BufReader, BufWriter},
13+
io::{self, BufReader, BufWriter},
1414
path::Path,
1515
};
1616
use url::Url;
1717

18-
pub(crate) fn convert_to_nt(
19-
file_paths: Vec<String>,
18+
pub(crate) fn convert_to_nt<P: AsRef<Path>>(
19+
file_paths: &[P],
2020
output_file: std::fs::File,
21-
) -> Result<(), Box<dyn Error>> {
21+
) -> Result<(), Error> {
2222
let mut dest_writer = BufWriter::new(output_file);
2323
for file in file_paths {
24-
let source = match std::fs::File::open(&file) {
25-
Ok(f) => f,
26-
Err(e) => {
27-
error!("Error opening file {file:?}: {e:?}");
28-
return Err(e.into());
29-
}
30-
};
24+
let file = file.as_ref();
25+
let source = std::fs::File::open(file).map_err(|e| {
26+
error!("Error opening file {}: {e:?}", file.display());
27+
e
28+
})?;
3129
let source_reader = BufReader::new(source);
3230

33-
debug!("converting {} to nt format", &file);
31+
debug!("converting {} to nt format", file.display());
3432

3533
let mut serializer = RdfSerializer::from_format(NTriples).for_writer(dest_writer.by_ref());
3634
let v = std::time::Instant::now();
37-
let ext = Path::new(&file)
38-
.extension()
39-
.and_then(|e| e.to_str())
40-
.ok_or_else(|| {
41-
std::io::Error::new(
42-
std::io::ErrorKind::InvalidInput,
43-
format!("file {file} has no usable extension"),
44-
)
45-
})?;
35+
let ext = file.extension().and_then(|e| e.to_str()).ok_or_else(|| {
36+
io::Error::new(
37+
io::ErrorKind::InvalidInput,
38+
format!("file {} has no usable extension", file.display()),
39+
)
40+
})?;
4641
let rdf_format = RdfFormat::from_extension(ext).ok_or_else(|| {
47-
error!("unrecognized file extension for {file}");
48-
std::io::Error::new(
49-
std::io::ErrorKind::InvalidData,
50-
format!("unrecognized file extension for {file}"),
42+
error!("unrecognized file extension for {}", file.display());
43+
io::Error::new(
44+
io::ErrorKind::InvalidData,
45+
format!("unrecognized file extension for {}", file.display()),
5146
)
5247
})?;
53-
let abs_path = std::fs::canonicalize(&file)?;
48+
let abs_path = std::fs::canonicalize(file)?;
5449
let base_iri = Url::from_file_path(&abs_path).map_err(|_| {
55-
std::io::Error::new(
56-
std::io::ErrorKind::InvalidInput,
57-
format!("cannot build file:// URI for {file}"),
50+
io::Error::new(
51+
io::ErrorKind::InvalidInput,
52+
format!("cannot build file:// URI for {}", file.display()),
5853
)
5954
})?;
6055
let quads = RdfParser::from_format(rdf_format)
61-
.with_base_iri(base_iri.as_str())?
56+
.with_base_iri(base_iri.as_str())
57+
.map_err(|e| Error::Parse(Box::new(e)))?
6258
.for_reader(source_reader);
6359
let mut warned = false;
6460
for q in quads {
6561
let q = match q {
6662
Ok(v) => v,
67-
Err(e) => {
68-
match e {
69-
RdfParseError::Io(v) => {
70-
// I/O error while reading file
71-
error!("Error reading file {file}: {v}");
72-
return Err(v.into());
73-
}
74-
RdfParseError::Syntax(syn_err) => {
75-
error!("syntax error for RDF file {file}: {syn_err}");
76-
return Err(syn_err.into());
77-
}
63+
Err(e) => match e {
64+
RdfParseError::Io(v) => {
65+
error!("Error reading file {}: {v}", file.display());
66+
return Err(v.into());
67+
}
68+
RdfParseError::Syntax(syn_err) => {
69+
error!("syntax error for RDF file {}: {syn_err}", file.display());
70+
return Err(Error::Parse(Box::new(syn_err)));
7871
}
79-
}
72+
},
8073
};
8174
if !warned && q.graph_name != oxrdf::GraphName::DefaultGraph {
8275
warned = true;
83-
warn!("HDT does not support named graphs, merging triples for {file}");
76+
warn!(
77+
"HDT does not support named graphs, merging triples for {}",
78+
file.display()
79+
);
8480
}
8581
serializer.serialize_triple(oxrdf::TripleRef {
8682
subject: q.subject.as_ref(),
@@ -106,7 +102,7 @@ mod tests {
106102
let tmp_file = tempfile::Builder::new().suffix(".nt").tempfile().expect("");
107103
assert!(
108104
(convert_to_nt(
109-
vec!["tests/resources/apple.ttl".to_string()],
105+
&["tests/resources/apple.ttl"],
110106
tmp_file.reopen().expect("error opening tmp file")
111107
))
112108
.is_ok()

0 commit comments

Comments
 (0)