From b77c8630656a68053a96dcd172d96b4ae672a447 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Thu, 20 May 2021 20:00:14 +0200 Subject: [PATCH] Some more error handling --- src/database.rs | 4 +- src/error.rs | 111 +++++++++++++++++++++++++++++++++++++++----- src/http/handler.rs | 34 +++++++------- src/http/parser.rs | 36 ++++++++------ src/main.rs | 23 ++++++--- src/usimp/mod.rs | 12 ++--- 6 files changed, 161 insertions(+), 59 deletions(-) diff --git a/src/database.rs b/src/database.rs index 5328b97..c523d70 100644 --- a/src/database.rs +++ b/src/database.rs @@ -35,8 +35,8 @@ pub fn init() -> Result<(), Error> { Ok(()) } -pub fn client() -> Client { +pub fn client() -> Result { match unsafe { POOL.as_ref().unwrap().clone().lock().unwrap().deref() } { - Pool::Postgres(pool) => Client::Postgres(pool.get().unwrap()), + Pool::Postgres(pool) => Ok(Client::Postgres(pool.get()?)), } } diff --git a/src/error.rs b/src/error.rs index ac1cfd7..26a85f3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,41 +1,113 @@ -pub enum ErrorKind { +use std::fmt; +use std::borrow::Borrow; + +#[derive(Copy, Clone)] +pub enum Kind { InvalidEndpointError, JsonParseError, + DatabaseConnectionError, DatabaseError, + HttpRequestParseError, + IoError, +} + +#[derive(Copy, Clone)] +pub enum Class { + ClientError, + ServerError, } pub struct Error { - kind: ErrorKind, + kind: Kind, + msg: Option, desc: Option, + class: Class, } impl Error { - pub fn new(kind: ErrorKind) -> Error { - Error { kind, desc: None } + pub fn new(kind: Kind, class: Class) -> Self { + Error { + kind, + msg: None, + desc: None, + class, + } + } + + pub fn class(&self) -> &Class { + &self.class + } + + pub fn set_msg(mut self, msg: String) -> Self { + self.msg = Some(msg); + self + } + + pub fn msg(&self) -> &str { + match &self.msg { + Some(msg) => msg.as_str(), + None => match self.kind { + Kind::InvalidEndpointError => "Invalid endpoint", + Kind::JsonParseError => "Unable to parse JSON data", + Kind::DatabaseConnectionError => "Unable to connect to database", + Kind::DatabaseError => "Database error", + Kind::HttpRequestParseError => "Unable to parse http request", + Kind::IoError => "IO error", + }, + } + } + + pub fn set_desc(mut self, desc: String) -> Self { + self.desc = Some(desc); + self + } + + pub fn desc(&self) -> Option<&str> { + match &self.desc { + Some(desc) => Some(desc.as_str()), + None => None, + } } } -impl ToString for Error { - fn to_string(&self) -> String { +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut error = match self.kind { - ErrorKind::InvalidEndpointError => "invalid endpoint", - ErrorKind::JsonParseError => "unable to parse JSON data", - ErrorKind::DatabaseError => "unable to connect to database", + Kind::InvalidEndpointError => "invalid endpoint", + Kind::JsonParseError => "unable to parse json data", + Kind::DatabaseConnectionError => "unable to connect to database", + Kind::DatabaseError => "database error", + Kind::HttpRequestParseError => "unable to parse http request", + Kind::IoError => "io error", } .to_string(); if let Some(desc) = &self.desc { error += ": "; error += desc; } - error + write!(f, "{}", error); + Ok(()) + } +} + +impl From for Error { + fn from(error: std::io::Error) -> Self { + Error { + kind: Kind::IoError, + msg: Some(error.to_string()), + desc: Some(error.to_string()), + class: Class::ClientError, + } } } impl From for Error { fn from(error: serde_json::Error) -> Self { Error { - kind: ErrorKind::JsonParseError, + kind: Kind::JsonParseError, + msg: Some("Unable to parse JSON data".to_string()), desc: Some(error.to_string()), + class: Class::ClientError, } } } @@ -43,8 +115,23 @@ impl From for Error { impl From for Error { fn from(error: r2d2::Error) -> Self { Error { - kind: ErrorKind::DatabaseError, + kind: Kind::DatabaseConnectionError, + msg: Some("Unable to connect to database".to_string()), desc: Some(error.to_string()), + class: Class::ServerError, + } + } +} + +impl From for Error { + fn from(error: r2d2_postgres::postgres::Error) -> Self { + // format: "db error: ERROR ..." + let msg = error.to_string().split(":").skip(1).collect::(); + Error { + kind: Kind::DatabaseError, + msg: Some("Database error".to_string()), + desc: Some(msg.trim().to_string()), + class: Class::ServerError, } } } diff --git a/src/http/handler.rs b/src/http/handler.rs index 8515d8a..d8dbc75 100644 --- a/src/http/handler.rs +++ b/src/http/handler.rs @@ -1,6 +1,7 @@ use super::Method; use crate::usimp; use crate::websocket; +use crate::error::*; use serde_json; pub struct HttpStream { @@ -69,8 +70,8 @@ fn request_handler(client: &mut super::HttpStream) { } Err(e) => { res.status(400); - res.error_info(format!("Unable to parser header: {}", &e)); - println!("Unable to parser header: {}", &e); + res.error_info(format!("{}", &e)); + println!("{}", &e); client.server_keep_alive = false; } } @@ -90,14 +91,17 @@ fn endpoint_handler( ) { res.add_header("Cache-Control", "no-store"); - let mut error = |code: u16, err_str: &str, client: &mut super::HttpStream| { - println!("{}", err_str); - res.status(code); - res.error_info(err_str.to_string()); + let mut error = |error: Error, client: &mut super::HttpStream| { + println!("{}", error.to_string()); + res.status(match &error.class() { + Class::ClientError => 400, + Class::ServerError => 500, + }); + res.error_info(error.to_string()); let mut obj = serde_json::Value::Object(serde_json::Map::new()); obj["status"] = serde_json::Value::String("error".to_string()); - obj["message"] = serde_json::Value::String(err_str.to_string()); + obj["message"] = serde_json::Value::String(error.to_string()); let buf = obj.to_string() + "\r\n"; let length = buf.as_bytes().len(); @@ -117,8 +121,8 @@ fn endpoint_handler( Some(length) => length, None => { return error( - 400, - "Unable to parse header: Content-Length missing", + Error::new(Kind::HttpRequestParseError, Class::ClientError) + .set_desc("field 'Content-Length' missing".to_string()), client, ) } @@ -128,8 +132,8 @@ fn endpoint_handler( Ok(length) => length, Err(e) => { return error( - 400, - format!("Unable to parse Content-Length: {}", &e).as_str(), + Error::new(Kind::HttpRequestParseError, Class::ClientError) + .set_desc(format!("unable to parse field 'Content-Length': {}", &e).to_string()), client, ) } @@ -142,17 +146,13 @@ fn endpoint_handler( let input = match serde_json::from_slice(&buf[..length]) { Ok(val) => val, Err(e) => { - return error( - 400, - format!("Unable to parse JSON: {}", &e).as_str(), - client, - ) + return error(e.into(), client) } }; let buf = match usimp::endpoint(endpoint, input) { Ok(output) => output.to_string() + "\r\n", - Err(e) => return error(500, e.to_string().as_str(), client), + Err(e) => return error(e, client), }; // TODO compress diff --git a/src/http/parser.rs b/src/http/parser.rs index c30707a..22a3046 100644 --- a/src/http/parser.rs +++ b/src/http/parser.rs @@ -1,9 +1,10 @@ +use crate::error::*; use crate::http; use crate::http::Status; -pub fn parse_request(stream: &mut http::Stream) -> Result, String> { +pub fn parse_request(stream: &mut http::Stream) -> Result, Error> { let mut buf = [0; 4096]; - let size = stream.peek(&mut buf).unwrap(); + let size = stream.peek(&mut buf)?; if size == 0 { return Ok(None); } @@ -26,22 +27,25 @@ pub fn parse_request(stream: &mut http::Stream) -> Result, header_fields, }; - stream.read_exact(&mut buf[..header_size]).unwrap(); + stream.read_exact(&mut buf[..header_size])?; Ok(Some(request)) } -pub fn parse_response(stream: &mut http::Stream) -> Result { +pub fn parse_response(stream: &mut http::Stream) -> Result { let mut buf = [0; 4096]; - let size = stream.peek(&mut buf).unwrap(); + let size = stream.peek(&mut buf)?; let mut parser = Parser::new_request_parser(&buf[..size]); - let header_size = parser.parse().unwrap(); + let header_size = parser.parse()?; let status_code = parser.status_code.unwrap(); - let status_code = match status_code.parse() { + let status_code = match status_code.parse::() { Ok(v) => v, - Err(e) => return Err(format!("{}", e)), + Err(error) => { + return Err(Error::new(Kind::HttpRequestParseError, Class::ClientError) + .set_desc(error.to_string())) + } }; let mut header_fields = Vec::new(); @@ -58,7 +62,7 @@ pub fn parse_response(stream: &mut http::Stream) -> Result { } } - fn parse(&mut self) -> Result { + fn parse(&mut self) -> Result { for char in self.buf { self.next(*char); match self.state { State::Finish => return Ok(self.header_size), State::Error => { - return Err(format!( - "invalid character at position {}", - self.header_size - 1 - )) + return Err(Error::new(Kind::HttpRequestParseError, Class::ClientError) + .set_desc(format!( + "invalid character at position {}", + self.header_size - 1 + ))) } _ => {} } } - return Err(String::from("input too short")); + return Err(Error::new(Kind::HttpRequestParseError, Class::ClientError) + .set_desc("input too short".to_string())); } fn next(&mut self, char: u8) { diff --git a/src/main.rs b/src/main.rs index c30a8ef..b5db678 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,11 +24,15 @@ enum SocketType { impl std::fmt::Display for SocketType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", match self { - SocketType::Http => "http+ws", - SocketType::Https => "https+wss", - SocketType::Udp => "udp", - }) + write!( + f, + "{}", + match self { + SocketType::Http => "http+ws", + SocketType::Https => "https+wss", + SocketType::Udp => "udp", + } + ) } } @@ -77,7 +81,9 @@ fn main() { eprintln!( "Creating listening thread for {} ({})", - ansi_term::Style::new().bold().paint(socket_config.address.to_string()), + ansi_term::Style::new() + .bold() + .paint(socket_config.address.to_string()), socket_config.socket_type ); @@ -100,7 +106,10 @@ fn main() { .set_certificate_chain_file("/home/lorenz/Certificates/chakotay.pem") .unwrap(); acceptor - .set_private_key_file("/home/lorenz/Certificates/priv/chakotay.key",SslFiletype::PEM) + .set_private_key_file( + "/home/lorenz/Certificates/priv/chakotay.key", + SslFiletype::PEM, + ) .unwrap(); acceptor.check_private_key().unwrap(); let acceptor = Arc::new(acceptor.build()); diff --git a/src/usimp/mod.rs b/src/usimp/mod.rs index 345b300..ba6d0ea 100644 --- a/src/usimp/mod.rs +++ b/src/usimp/mod.rs @@ -6,8 +6,8 @@ use crate::error::*; pub fn endpoint(endpoint: &str, input: serde_json::Value) -> Result { match endpoint { - "echo" => Ok(serde_json::to_value(echo(serde_json::from_value(input)?))?), - _ => Err(Error::new(ErrorKind::InvalidEndpointError)), + "echo" => Ok(serde_json::to_value(echo(serde_json::from_value(input)?)?)?), + _ => Err(Error::new(Kind::InvalidEndpointError, Class::ClientError)), } } @@ -22,19 +22,19 @@ pub struct EchoOutput { database: Option, } -pub fn echo(input: EchoInput) -> EchoOutput { - let backend = database::client(); +pub fn echo(input: EchoInput) -> Result { + let backend = database::client()?; let mut output = EchoOutput { message: input.message, database: None, }; match backend { database::Client::Postgres(mut client) => { - let res = client.query("SELECT * FROM test", &[]).unwrap(); + let res = client.query("SELECT * FROM test", &[])?; for row in res { output.database = Some(row.get(0)); } } } - output + Ok(output) }