Skip to content

Commit

Permalink
feat: handle io and fmt errors
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Oct 7, 2024
1 parent 8f24656 commit 643aa09
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 54 deletions.
23 changes: 22 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition = "2021"
[dependencies]
clap = { version = "4.5.17", features = ["derive"] }
hex = "0.4"
thiserror = "1.0.64"

[dev-dependencies]
assert_cmd = "2.0"
1 change: 1 addition & 0 deletions project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ llee
llleee
spame
spamee
thiserror
8 changes: 5 additions & 3 deletions src/io/byte_reader.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use core::str;
use std::io::{self, Read};
use std::io::Read;

use super::error::Error;

/// A reader that reads bytes from an input.
///
Expand Down Expand Up @@ -28,7 +30,7 @@ impl<R: Read> ByteReader<R> {
/// # Errors
///
/// Will return an error if it can't read the byte from the input.
pub fn read_byte(&mut self) -> io::Result<u8> {
pub fn read_byte(&mut self) -> Result<u8, Error> {
let mut byte = [0; 1];

self.reader.read_exact(&mut byte)?;
Expand All @@ -44,7 +46,7 @@ impl<R: Read> ByteReader<R> {
Ok(byte)
}

/// It prints the captured input is enabled.
/// It prints the captured input if enabled.
///
/// It will print a string it the captured input so far is a UTF-8 string,
/// the debug info otherwise.
Expand Down
8 changes: 4 additions & 4 deletions src/io/byte_writer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::str;
use std::io::{self, Write};
use std::io::Write;

use super::writer::Writer;
use super::{error::Error, writer::Writer};

/// A writer that writes bytes to an output.
///
Expand All @@ -27,7 +27,7 @@ impl<W: Write> ByteWriter<W> {
}

impl<W: Write> Writer for ByteWriter<W> {
fn write_byte(&mut self, byte: u8) -> io::Result<()> {
fn write_byte(&mut self, byte: u8) -> Result<(), Error> {
let bytes = [byte];

self.writer.write_all(&bytes)?;
Expand All @@ -41,7 +41,7 @@ impl<W: Write> Writer for ByteWriter<W> {
Ok(())
}

fn write_str(&mut self, value: &str) -> io::Result<()> {
fn write_str(&mut self, value: &str) -> Result<(), Error> {
self.writer.write_all(value.as_bytes())?;

self.output_byte_counter += value.as_bytes().len() as u64;
Expand Down
12 changes: 12 additions & 0 deletions src/io/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use std::{fmt, io};
use thiserror::Error;

/// Custom error type for both I/O and formatting errors.
#[derive(Debug, Error)]
pub enum Error {
#[error("I/O error: {0}")]
Io(#[from] io::Error),

#[error("Formatting error: {0}")]
Fmt(#[from] fmt::Error),
}
1 change: 1 addition & 0 deletions src/io/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod byte_reader;
pub mod byte_writer;
pub mod error;
pub mod string_writer;
pub mod writer;
8 changes: 4 additions & 4 deletions src/io/string_writer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::str;
use std::{fmt::Write, io};
use std::fmt::Write;

use super::writer::Writer;
use super::{error::Error, writer::Writer};

/// A writer that writes chars to an output.
///
Expand All @@ -27,7 +27,7 @@ impl<W: Write> StringWriter<W> {
}

impl<W: Write> Writer for StringWriter<W> {
fn write_byte(&mut self, byte: u8) -> io::Result<()> {
fn write_byte(&mut self, byte: u8) -> Result<(), Error> {
let c = byte as char;

self.writer.write_char(c).expect("error writing str");
Expand All @@ -41,7 +41,7 @@ impl<W: Write> Writer for StringWriter<W> {
Ok(())
}

fn write_str(&mut self, value: &str) -> io::Result<()> {
fn write_str(&mut self, value: &str) -> Result<(), Error> {
self.writer.write_str(value).expect("error writing str");

self.output_string_length += value.len() as u64;
Expand Down
8 changes: 4 additions & 4 deletions src/io/writer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::io;

/* code-review:
The function `write_byte` only writes:
Expand All @@ -14,20 +12,22 @@ use std::io;
them into UTF-8 strings.
*/

use super::error::Error;

pub trait Writer {
/// It writes one byte to the output.
///
/// # Errors
///
/// Will return an error if it can't write the byte.
fn write_byte(&mut self, byte: u8) -> io::Result<()>;
fn write_byte(&mut self, byte: u8) -> Result<(), Error>;

/// It writes a string to the output.
///
/// # Errors
///
/// Will return an error if it can't write the string.
fn write_str(&mut self, value: &str) -> io::Result<()>;
fn write_str(&mut self, value: &str) -> Result<(), Error>;

/// It gets the captured output if enabled.
fn get_captured_output(&mut self) -> Option<String>;
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fs::File;
use std::io::{self, Read, Write};
use torrust_bencode2json::parsers::BencodeParser;

fn main() -> io::Result<()> {
fn main() -> Result<(), torrust_bencode2json::io::error::Error> {
let matches = Command::new("torrust-bencode2json")
.version("0.1.0")
.author("Torrust Organization")
Expand Down
9 changes: 3 additions & 6 deletions src/parsers/integer.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! Bencoded integer parser.
//!
//! It reads bencoded bytes from the input and writes JSON bytes to the output.
use std::io::{self, Read};
use std::io::Read;

use crate::io::{byte_reader::ByteReader, writer::Writer};
use crate::io::{byte_reader::ByteReader, error::Error, writer::Writer};

// code-review: state machine to check state transitions (runtime or compile time)?

Expand Down Expand Up @@ -31,15 +31,12 @@ pub fn parse<R: Read, W: Writer>(
reader: &mut ByteReader<R>,
writer: &mut W,
_initial_byte: u8,
) -> io::Result<()> {
) -> Result<(), Error> {
let mut state = StateExpecting::DigitOrSign;

loop {
let byte = match reader.read_byte() {
Ok(byte) => byte,
Err(ref err) if err.kind() == io::ErrorKind::UnexpectedEof => {
panic!("unexpected end of input parsing integer");
}
Err(err) => return Err(err),
};

Expand Down
58 changes: 40 additions & 18 deletions src/parsers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use std::{
use stack::{Stack, State};

use crate::io::{
byte_reader::ByteReader, byte_writer::ByteWriter, string_writer::StringWriter, writer::Writer,
byte_reader::ByteReader, byte_writer::ByteWriter, error::Error, string_writer::StringWriter,
writer::Writer,
};

pub struct BencodeParser<R: Read> {
Expand Down Expand Up @@ -40,7 +41,7 @@ impl<R: Read> BencodeParser<R> {
}

/// It parses a bencoded value read from input and writes the corresponding
/// JSON value to the output.
/// JSON UTF-8 string value to the output.
///
/// # Errors
///
Expand All @@ -51,13 +52,13 @@ impl<R: Read> BencodeParser<R> {
///
/// Will panic if receives a byte that isn't a valid begin or end of a
/// bencoded type: integer, string, list or dictionary.
pub fn write_bytes<W: IoWrite>(&mut self, writer: W) -> io::Result<()> {
let mut writer = ByteWriter::new(writer);
pub fn write_str<W: FmtWrite>(&mut self, writer: W) -> Result<(), crate::io::error::Error> {
let mut writer = StringWriter::new(writer);
self.parse(&mut writer)
}

/// It parses a bencoded value read from input and writes the corresponding
/// JSON value to the output.
/// JSON UTF-8 string value as bytes to the output.
///
/// # Errors
///
Expand All @@ -68,8 +69,8 @@ impl<R: Read> BencodeParser<R> {
///
/// Will panic if receives a byte that isn't a valid begin or end of a
/// bencoded type: integer, string, list or dictionary.
pub fn write_str<W: FmtWrite>(&mut self, writer: W) -> io::Result<()> {
let mut writer = StringWriter::new(writer);
pub fn write_bytes<W: IoWrite>(&mut self, writer: W) -> Result<(), crate::io::error::Error> {
let mut writer = ByteWriter::new(writer);
self.parse(&mut writer)
}

Expand All @@ -85,15 +86,16 @@ impl<R: Read> BencodeParser<R> {
///
/// Will panic if receives a byte that isn't a valid begin or end of a
/// bencoded type: integer, string, list or dictionary.
fn parse<W: Writer>(&mut self, writer: &mut W) -> io::Result<()> {
fn parse<W: Writer>(&mut self, writer: &mut W) -> Result<(), crate::io::error::Error> {
loop {
let byte = match self.byte_reader.read_byte() {
Ok(byte) => byte,
Err(ref err) if err.kind() == io::ErrorKind::UnexpectedEof => {
//println!("Reached the end of file.");
break;
}
Err(err) => return Err(err),
Err(err) => match err {
Error::Io(ref io_error) if io_error.kind() == io::ErrorKind::UnexpectedEof => {
break;
}
Error::Io(_) | Error::Fmt(_) => return Err(err),
},
};

if self.debug {
Expand Down Expand Up @@ -158,7 +160,10 @@ impl<R: Read> BencodeParser<R> {
/// # Errors
///
/// Will return an error if the writer can't write to the output.
pub fn begin_bencoded_value<W: Writer>(&mut self, writer: &mut W) -> io::Result<()> {
pub fn begin_bencoded_value<W: Writer>(
&mut self,
writer: &mut W,
) -> Result<(), crate::io::error::Error> {
match self.stack.peek() {
State::Initial => {}
State::ExpectingFirstListItemOrEnd => {
Expand Down Expand Up @@ -196,7 +201,10 @@ impl<R: Read> BencodeParser<R> {
///
/// Will panic if the end of bencoded value (list or dictionary) was not
/// expected.
pub fn end_bencoded_value<W: Writer>(&mut self, writer: &mut W) -> io::Result<()> {
pub fn end_bencoded_value<W: Writer>(
&mut self,
writer: &mut W,
) -> Result<(), crate::io::error::Error> {
match self.stack.peek() {
State::ExpectingFirstListItemOrEnd | State::ExpectingNextListItem => {
writer.write_byte(Self::JSON_ARRAY_END)?;
Expand Down Expand Up @@ -251,16 +259,17 @@ mod tests {
assert_eq!(output, "0".to_string());
}

fn to_json(input_buffer: &[u8]) -> String {
#[test]
fn it_should_allow_an_empty_input() {
let mut output = String::new();

let mut parser = BencodeParser::new(input_buffer);
let mut parser = BencodeParser::new(&b""[..]);

parser
.write_str(&mut output)
.expect("Bencode to JSON conversion failed");

output
assert_eq!(output, String::new());
}

mod integers {
Expand Down Expand Up @@ -570,4 +579,17 @@ mod tests {
}
}
}

/// Wrapper to easily use the parser in tests
fn to_json(input_buffer: &[u8]) -> String {
let mut output = String::new();

let mut parser = BencodeParser::new(input_buffer);

parser
.write_str(&mut output)
.expect("Bencode to JSON conversion failed");

output
}
}
Loading

0 comments on commit 643aa09

Please sign in to comment.