feat(fs)!: Reader now supports non-utf8 files
All checks were successful
Test / build (map[name:nightly]) (push) Successful in 5m53s
Test / build (map[name:stable]) (push) Successful in 8m19s
Release Please / Release-plz (push) Successful in 37s

- reader() now returns a Result
- Reader::as_str() now returns a Result
- Reader::lines() now returns a Result
This commit is contained in:
Paul Campbell 2024-12-15 13:45:05 +00:00
parent c38a6c79c4
commit 781f9e8043
4 changed files with 17 additions and 13 deletions

View file

@ -89,7 +89,7 @@ fn read_file(file: &FileHandle) -> kxio::Result<()> {
// Creates a `Reader` which loaded the file into memory. // Creates a `Reader` which loaded the file into memory.
let reader: kxio::fs::Reader = file.reader()?; let reader: kxio::fs::Reader = file.reader()?;
let contents: &str = reader.as_str(); let contents: &str = reader.as_str()?;
println!("{contents}"); println!("{contents}");
Ok(()) Ok(())
@ -106,6 +106,7 @@ fn delete_file(file: FileHandle) -> kxio::Result<()> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use assert2::let_assert;
use http::StatusCode; use http::StatusCode;
use super::*; use super::*;
@ -146,7 +147,7 @@ mod tests {
// Read the file // Read the file
let file = fs.file(&file_path); let file = fs.file(&file_path);
let reader = file.reader().expect("reader"); let reader = file.reader().expect("reader");
let contents = reader.as_str(); let_assert!(Ok(contents) = reader.as_str());
assert_eq!(contents, "contents"); assert_eq!(contents, "contents");

View file

@ -8,13 +8,13 @@ use super::Error;
/// A reader for a file. /// A reader for a file.
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Reader { pub struct Reader {
contents: String, contents: Vec<u8>,
} }
impl Reader { impl Reader {
#[tracing::instrument] #[tracing::instrument]
pub(super) fn new(path: &Path) -> Result<Self> { pub(super) fn new(path: &Path) -> Result<Self> {
tracing::debug!("std::fs::read_to_string"); tracing::debug!("std::fs::read_to_string");
let contents = std::fs::read_to_string(path).map_err(Error::Io)?; let contents = std::fs::read(path).map_err(Error::Io)?;
tracing::debug!(len = contents.len(), "contents"); tracing::debug!(len = contents.len(), "contents");
Ok(Self { contents }) Ok(Self { contents })
} }
@ -32,8 +32,8 @@ impl Reader {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> Result<&str> {
&self.contents std::str::from_utf8(&self.contents).map_err(Error::Utf8Error)
} }
/// Returns an iterator over the lines in the file. /// Returns an iterator over the lines in the file.
@ -49,8 +49,8 @@ impl Reader {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn lines(&self) -> Lines<'_> { pub fn lines(&self) -> Result<Lines<'_>> {
self.contents.lines() self.as_str().map(|s| s.lines())
} }
/// Returns the contents of the file as bytes. /// Returns the contents of the file as bytes.
@ -67,11 +67,11 @@ impl Reader {
/// # } /// # }
/// ``` /// ```
pub fn bytes(&self) -> &[u8] { pub fn bytes(&self) -> &[u8] {
self.contents.as_bytes() &self.contents
} }
} }
impl Display for Reader { impl Display for Reader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.contents) write!(f, "{}", self.as_str().map_err(|_| std::fmt::Error)?)
} }
} }

View file

@ -12,6 +12,8 @@ pub enum Error {
IoString(String), IoString(String),
Utf8Error(std::str::Utf8Error),
#[display("Path access attempted outside of base ({base:?}): {path:?}")] #[display("Path access attempted outside of base ({base:?}): {path:?}")]
PathTraversal { PathTraversal {
base: PathBuf, base: PathBuf,
@ -34,6 +36,7 @@ impl Clone for Error {
match self { match self {
Error::Io(err) => Error::IoString(err.to_string()), Error::Io(err) => Error::IoString(err.to_string()),
Error::IoString(err) => Error::IoString(err.clone()), Error::IoString(err) => Error::IoString(err.clone()),
Error::Utf8Error(err) => Error::Utf8Error(*err),
Error::PathTraversal { base, path } => Error::PathTraversal { Error::PathTraversal { base, path } => Error::PathTraversal {
base: base.clone(), base: base.clone(),
path: path.clone(), path: path.clone(),

View file

@ -723,7 +723,7 @@ mod file {
file.write(&contents).expect("write"); file.write(&contents).expect("write");
let reader = file.reader().expect("reader"); let reader = file.reader().expect("reader");
let string = reader.as_str(); let string = reader.as_str().expect("as_str");
assert_eq!(string, contents); assert_eq!(string, contents);
} }
@ -743,7 +743,7 @@ mod file {
file.write(&contents).expect("write"); file.write(&contents).expect("write");
let reader = file.reader().expect("reader"); let reader = file.reader().expect("reader");
let lines = reader.lines().collect::<Vec<_>>(); let lines = reader.lines().expect("lines").collect::<Vec<_>>();
assert_eq!(lines, vec!["line 1", "line 2", &line3]); assert_eq!(lines, vec!["line 1", "line 2", &line3]);
} }
@ -759,7 +759,7 @@ mod file {
file.write("line 1\nline 2").expect("write"); file.write("line 1\nline 2").expect("write");
let reader = file.reader().expect("reader"); let reader = file.reader().expect("reader");
let lines = reader.lines().collect::<Vec<_>>(); let lines = reader.lines().expect("lines").collect::<Vec<_>>();
assert_eq!(lines, vec!["line 1", "line 2"]); assert_eq!(lines, vec!["line 1", "line 2"]);