diff --git a/src/lib.rs b/src/lib.rs index e03a68d..7370b02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,10 +36,11 @@ //! println!("RESULT = {:#?}", res); //! ``` +use thiserror::Error; + #[cfg(feature = "embedded-server")] use crate::server::EmbeddedServer; use std::collections::HashMap; -use std::error::Error; #[cfg(feature = "embedded-server")] pub mod server; @@ -302,6 +303,28 @@ pub struct SuggestResult { pub suggestions: Vec, } +#[derive(Debug, Error)] +pub enum Error { + #[cfg(feature = "embedded-server")] + #[error("Grammalecte-server failed to start")] + ServerStartFailed(#[from] server::Error), + + #[error("Failed to Serialize Option in Json")] + OptionJsonSerialization(#[source] serde_json::Error), + + #[error("Failed to send request `check with option`")] + RequestSendCheckWithOptions(#[source] reqwest::Error), + + #[error("Failed to send request `suggest`")] + RequestSendSuggest(#[source] reqwest::Error), + + #[error("Failed to Deserialize Check result")] + CheckResultDeserialize(#[source] reqwest::Error), + + #[error("Failed to Deserialize Suggest result")] + SuggestDeserialize(#[source] reqwest::Error), +} + /// The Grammalecte client itself pub struct GrammalecteClient { base_url: String, @@ -335,7 +358,7 @@ impl GrammalecteClient { /// /// Python 3.7 or higher must is required at runtime #[cfg(feature = "embedded-server")] - pub fn start_server() -> Result> { + pub fn start_server() -> Result { let server = EmbeddedServer::start()?; Ok(Self { base_url: server.base_url(), @@ -344,7 +367,7 @@ impl GrammalecteClient { } /// Run spell check on text - pub async fn spell_check(&self, text: &str) -> Result> { + pub async fn spell_check(&self, text: &str) -> Result { self.spell_check_with_options(text, &HashMap::new()).await } @@ -353,7 +376,7 @@ impl GrammalecteClient { &self, text: &str, options: &HashMap, - ) -> Result> { + ) -> Result { let url = format!("{}/gc_text/fr", self.base_url); log::debug!("Will use URL {} for spell check", url); @@ -361,7 +384,7 @@ impl GrammalecteClient { .iter() .map(|t| (t.0.id(), t.1)) .collect::>(); - let options = serde_json::to_string(&options)?; + let options = serde_json::to_string(&options).map_err(Error::OptionJsonSerialization)?; let mut params = HashMap::new(); params.insert("text", text); @@ -371,15 +394,17 @@ impl GrammalecteClient { .post(url) .form(¶ms) .send() - .await? + .await + .map_err(Error::RequestSendCheckWithOptions)? .json::() - .await?; + .await + .map_err(Error::CheckResultDeserialize)?; Ok(result) } /// Ask for word suggestion - pub async fn suggest(&self, token: &str) -> Result> { + pub async fn suggest(&self, token: &str) -> Result { let url = format!("{}/suggest/fr", self.base_url); log::debug!("Will use URL {} for word suggestion", url); @@ -390,9 +415,11 @@ impl GrammalecteClient { .post(&url) .form(¶ms) .send() - .await? + .await + .map_err(Error::RequestSendSuggest)? .json() - .await?) + .await + .map_err(Error::SuggestDeserialize)?) } } diff --git a/src/server.rs b/src/server.rs index ea7f312..be2a881 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,10 +1,44 @@ use crate::server::utils::{get_free_port, wait_for_port}; use mktemp::Temp; -use std::error::Error; -use std::io::{Cursor, Read}; +use std::io::{self, Cursor, Read}; use std::process::{Child, Stdio}; +use thiserror::Error; +use zip::result::ZipError; use zip::ZipArchive; +#[derive(Debug, Error)] +pub enum Error { + #[error("Grammalecte-server failed to launch process")] + StartServerProcess(#[source] io::Error), + + #[error("Failed to get free port")] + GetFreePort(#[source] io::Error), + + #[error("Port {port} did not open in time!")] + WaitPortOpen { port: u16 }, + + #[error("Failed create temporary directory")] + CreateTempDir(#[source] io::Error), + + #[error("TODO")] + ZipArchive(#[source] ZipError), + + #[error("TODO")] + ZipFileIndex(#[source] ZipError), + + // TODO: rename + #[error("TODO")] + CreateDirectory(#[source] io::Error), + + // TODO: rename + #[error("TODO")] + ZipFileReadToEnd(#[source] io::Error), + + // TODO: rename + #[error("TODO")] + WriteFile(#[source] io::Error), +} + pub struct EmbeddedServer { _srv_dir: Temp, port: u16, @@ -13,29 +47,30 @@ pub struct EmbeddedServer { impl EmbeddedServer { /// Start embedded Grammalecte server on a random free port - pub fn start() -> Result> { - Self::start_listen_on_port(get_free_port()?) + pub fn start() -> Result { + Self::start_listen_on_port(get_free_port()) } /// Start embedded Grammalecte server on a given port - pub fn start_listen_on_port(port: u16) -> Result> { + pub fn start_listen_on_port(port: u16) -> Result { log::info!("Will start server"); // First, unpack server - let dest = mktemp::Temp::new_dir()?; + let dest = mktemp::Temp::new_dir().map_err(Error::CreateTempDir)?; let cursor = Cursor::new(include_bytes!("GrammalecteDist.zip")); - let mut zip = ZipArchive::new(cursor)?; + let mut zip = ZipArchive::new(cursor).map_err(Error::ZipArchive)?; for i in 0..zip.len() { - let mut file = zip.by_index(i)?; + let mut file = zip.by_index(i).map_err(Error::ZipFileIndex)?; if file.is_dir() { log::debug!("Create directory: {}", file.name()); - std::fs::create_dir_all(dest.join(file.name()))?; + std::fs::create_dir_all(dest.join(file.name())).map_err(Error::CreateDirectory)?; } else { log::debug!("Decompress file: {}", file.name()); let mut buff = Vec::with_capacity(file.size() as usize); - file.read_to_end(&mut buff)?; + file.read_to_end(&mut buff) + .map_err(Error::ZipFileReadToEnd)?; - std::fs::write(dest.join(file.name()), buff)?; + std::fs::write(dest.join(file.name()), buff).map_err(Error::WriteFile)?; } } @@ -54,7 +89,8 @@ impl EmbeddedServer { .arg(port.to_string()) .stdout(Stdio::null()) .stderr(Stdio::null()) - .spawn()?; + .spawn() + .map_err(Error::StartServerProcess)?; wait_for_port(port)?; @@ -78,11 +114,12 @@ impl Drop for EmbeddedServer { } mod utils { - use std::io::ErrorKind; use std::time::Duration; + use super::Error; + /// Get a free port - pub fn get_free_port() -> std::io::Result { + pub fn get_free_port() -> u16 { let mut port = 0; while !(2000..=64000).contains(&port) { @@ -93,10 +130,10 @@ mod utils { port += 1; } - Ok(port) + port } - pub fn wait_for_port(port: u16) -> std::io::Result<()> { + pub fn wait_for_port(port: u16) -> Result<(), Error> { for _ in 0..50 { if port_scanner::scan_port(port) { return Ok(()); @@ -104,9 +141,6 @@ mod utils { std::thread::sleep(Duration::from_millis(100)); } - Err(std::io::Error::new( - ErrorKind::Other, - format!("Port {} did not open in time!", port), - ))? + Err(Error::WaitPortOpen { port }) } }