diff --git a/src/client.rs b/src/client.rs index 59c10e3..e91765f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,7 +11,8 @@ use std::io::{self, Read, Write}; use crate::constants::*; use crate::protocol::{ - flush_lines, parse_event_id, parse_single_value, parse_typed_lines, write_lines, + flush_lines, parse_event_id, parse_single_integer, parse_single_value, parse_typed_lines, + write_lines, }; use crate::types::*; @@ -132,7 +133,7 @@ pub enum Response { HistoryLastMsg(String), // 242 HistoryCurPosRet(String), // 243 TableListSent(Vec), // 244 - HistoryClientIdSent(String), // 245 + HistoryClientIdSent(ClientId), // 245 MessageTextSent, // 246 HelpSent(Vec), // 248 VoicesListSent(Vec), // 249 @@ -653,7 +654,7 @@ impl Client { OK_LAST_MSG => Ok(Response::HistoryLastMsg(parse_single_value(&lines)?)), OK_CUR_POS_RET => Ok(Response::HistoryCurPosRet(parse_single_value(&lines)?)), OK_TABLE_LIST_SENT => Ok(Response::TableListSent(lines)), - OK_CLIENT_ID_SENT => Ok(Response::HistoryClientIdSent(parse_single_value(&lines)?)), + OK_CLIENT_ID_SENT => Ok(Response::HistoryClientIdSent(parse_single_integer(&lines)?)), OK_MSG_TEXT_SENT => Ok(Response::MessageTextSent), OK_HELP_SENT => Ok(Response::HelpSent(lines)), OK_VOICES_LIST_SENT => Ok(Response::VoicesListSent( @@ -711,20 +712,26 @@ impl Client { /// Receive signed 8-bit integer pub fn receive_i8(&mut self) -> ClientResult { - self.receive_string(OK_GET) - .and_then(|s| s.parse().map_err(|_| ClientError::InvalidType)) + self.receive_string(OK_GET).and_then(|s| { + s.parse() + .map_err(|_| ClientError::invalid_data("invalid signed integer")) + }) } /// Receive unsigned 8-bit integer pub fn receive_u8(&mut self) -> ClientResult { - self.receive_string(OK_GET) - .and_then(|s| s.parse().map_err(|_| ClientError::InvalidType)) + self.receive_string(OK_GET).and_then(|s| { + s.parse() + .map_err(|_| ClientError::invalid_data("invalid unsigned integer")) + }) } /// Receive message id pub fn receive_message_id(&mut self) -> ClientResult { - self.receive_string(OK_MESSAGE_QUEUED) - .and_then(|s| s.parse().map_err(|_| ClientError::InvalidType)) + self.receive_string(OK_MESSAGE_QUEUED).and_then(|s| { + s.parse() + .map_err(|_| ClientError::invalid_data("invalid message id")) + }) } /// Receive a list of synthesis voices @@ -738,14 +745,14 @@ impl Client { let mut lines = Vec::new(); crate::protocol::receive_answer(&mut self.input, Some(&mut lines)).and_then(|status| { if lines.len() < 2 { - Err(ClientError::TruncatedMessage) + Err(ClientError::unexpected_eof("event truncated")) } else { let message = &lines[0]; let client = &lines[1]; match status.code { 700 => { if lines.len() != 3 { - Err(ClientError::TruncatedMessage) + Err(ClientError::unexpected_eof("index markevent truncated")) } else { let mark = lines[3].to_owned(); Ok(Event::index_mark(mark, message, client)) @@ -756,7 +763,7 @@ impl Client { 703 => Ok(Event::cancel(message, client)), 704 => Ok(Event::pause(message, client)), 705 => Ok(Event::resume(message, client)), - _ => Err(ClientError::InvalidType), + _ => Err(ClientError::invalid_data("wrong status code for event")), } } }) diff --git a/src/protocol.rs b/src/protocol.rs index 920486b..19d8f0f 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -40,13 +40,26 @@ pub(crate) fn parse_event_id(lines: &[String]) -> ClientResult { } } +/// Parse single integer value +pub(crate) fn parse_single_integer(lines: &[String]) -> ClientResult +where + T: FromStr, +{ + parse_single_value(lines)?.parse::().map_err(|_| { + ClientError::Io(io::Error::new( + io::ErrorKind::InvalidData, + "invalid integer value", + )) + }) +} + pub(crate) fn parse_typed_lines(lines: &[String]) -> ClientResult> where - T: FromStr, + T: FromStr, { lines .iter() - .map(|line| T::from_str(line.as_str()).map_err(|err| ClientError::from(err))) + .map(|line| T::from_str(line.as_str())) .collect::>>() } diff --git a/src/types.rs b/src/types.rs index fb51fc7..18e4109 100644 --- a/src/types.rs +++ b/src/types.rs @@ -369,15 +369,18 @@ impl SynthesisVoice { } impl FromStr for SynthesisVoice { - type Err = io::Error; + type Err = ClientError; fn from_str(s: &str) -> Result { let mut iter = s.split('\t'); - Ok(SynthesisVoice { - name: String::from(iter.next().unwrap()), - language: SynthesisVoice::parse_none(iter.next()), - dialect: SynthesisVoice::parse_none(iter.next()), - }) + match iter.next() { + Some(name) => Ok(SynthesisVoice { + name: name.to_string(), + language: SynthesisVoice::parse_none(iter.next()), + dialect: SynthesisVoice::parse_none(iter.next()), + }), + None => Err(ClientError::unexpected_eof("missing synthesis voice name")), + } } } @@ -402,8 +405,6 @@ impl fmt::Display for StatusLine { /// Client error, either I/O error or SSIP error. #[derive(ThisError, Debug)] pub enum ClientError { - #[error("Invalid type")] - InvalidType, #[error("I/O: {0}")] Io(io::Error), #[error("Not ready")] @@ -414,12 +415,27 @@ pub enum ClientError { TooFewLines, #[error("Too many lines")] TooManyLines, - #[error("Truncated message")] - TruncatedMessage, #[error("Unexpected status: {0}")] UnexpectedStatus(ReturnCode), } +impl ClientError { + /// Create I/O error + pub(crate) fn io_error(kind: io::ErrorKind, msg: &str) -> Self { + Self::Io(io::Error::new(kind, msg)) + } + + /// Invalid data I/O error + pub(crate) fn invalid_data(msg: &str) -> Self { + ClientError::io_error(io::ErrorKind::InvalidData, msg) + } + + /// Unexpected EOF I/O error + pub(crate) fn unexpected_eof(msg: &str) -> Self { + ClientError::io_error(io::ErrorKind::UnexpectedEof, msg) + } +} + impl From for ClientError { fn from(err: io::Error) -> Self { if err.kind() == io::ErrorKind::WouldBlock { @@ -538,26 +554,15 @@ impl HistoryClientStatus { connected, } } - - fn invalid_data(msg: &str) -> io::Error { - io::Error::new(io::ErrorKind::InvalidData, msg) - } - - fn unexpected_eof(msg: &str) -> io::Error { - io::Error::new(io::ErrorKind::UnexpectedEof, msg) - } } impl FromStr for HistoryClientStatus { - type Err = io::Error; + type Err = ClientError; fn from_str(s: &str) -> Result { let mut iter = s.splitn(3, ' '); match iter.next() { - Some("") => Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "expecting client id", - )), + Some("") => Err(ClientError::unexpected_eof("expecting client id")), Some(client_id) => match client_id.parse::() { Ok(id) => match iter.next() { Some(name) => match iter.next() { @@ -567,16 +572,14 @@ impl FromStr for HistoryClientStatus { Some(status) if status == "1" => { Ok(HistoryClientStatus::new(id, name, true)) } - Some(_) => Err(HistoryClientStatus::invalid_data("invalid client status")), - None => Err(HistoryClientStatus::unexpected_eof( - "expecting client status", - )), + Some(_) => Err(ClientError::invalid_data("invalid client status")), + None => Err(ClientError::unexpected_eof("expecting client status")), }, - None => Err(HistoryClientStatus::unexpected_eof("expecting client name")), + None => Err(ClientError::unexpected_eof("expecting client name")), }, - Err(_) => Err(HistoryClientStatus::invalid_data("invalid client id")), + Err(_) => Err(ClientError::invalid_data("invalid client id")), }, - None => Err(HistoryClientStatus::unexpected_eof("expecting client id")), + None => Err(ClientError::unexpected_eof("expecting client id")), } } } @@ -587,7 +590,7 @@ mod tests { use std::io; use std::str::FromStr; - use super::{HistoryClientStatus, HistoryPosition, MessageScope, SynthesisVoice}; + use super::{ClientError, HistoryClientStatus, HistoryPosition, MessageScope, SynthesisVoice}; #[test] fn parse_synthesis_voice() { @@ -635,23 +638,15 @@ mod tests { ] { match HistoryClientStatus::from_str(line) { Ok(_) => panic!("parsing should have failed"), - Err(err) => assert_eq!( - io::ErrorKind::InvalidData, - err.kind(), - "expecting error 'invalid data' parsing \"{}\"", - line - ), + Err(ClientError::Io(err)) if err.kind() == io::ErrorKind::InvalidData => (), + Err(_) => panic!("expecting error 'invalid data' parsing \"{}\"", line), } } for line in &["8 joe:speechd_client:main", "8", ""] { match HistoryClientStatus::from_str(line) { Ok(_) => panic!("parsing should have failed"), - Err(err) => assert_eq!( - io::ErrorKind::UnexpectedEof, - err.kind(), - "expecting error 'unexpected EOF' parsing \"{}\"", - line - ), + Err(ClientError::Io(err)) if err.kind() == io::ErrorKind::UnexpectedEof => (), + Err(_) => panic!("expecting error 'unexpected EOF' parsing \"{}\"", line), } } }