diff --git a/src/api_info.rs b/src/api_info.rs index 44013c84..cc9f2b38 100644 --- a/src/api_info.rs +++ b/src/api_info.rs @@ -1,9 +1,10 @@ +use std::fmt; use failure::*; use futures::future::*; use crate::json_schema::*; use serde_json::{Value}; -use hyper::{Body, Response}; +use hyper::{Body, Response, StatusCode}; use std::collections::HashMap; @@ -15,6 +16,24 @@ pub struct ApiMethod { pub async_handler: fn(Value, &ApiMethod) -> Box, Error = Error> + Send> } +#[derive(Debug, Fail)] +pub struct ApiError { + pub code: StatusCode, + pub message: String, +} + +impl ApiError { + pub fn new(code: StatusCode, message: String) -> Self { + ApiError { code, message } + } +} + +impl fmt::Display for ApiError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Error {}: {}", self.code, self.message) + } +} + pub struct MethodInfo { pub get: Option, pub put: Option, diff --git a/src/main.rs b/src/main.rs index 85bbcf8b..f9dde441 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,8 @@ extern crate apitest; use failure::*; use std::collections::HashMap; //use std::io; -use std::fs; -use std::path::{Path,PathBuf}; +//use std::fs; +use std::path::{PathBuf}; //use std::collections::HashMap; use lazy_static::lazy_static; @@ -16,7 +16,7 @@ use apitest::json_schema::*; //use serde_derive::{Serialize, Deserialize}; use serde_json::{json, Value}; -use futures::future::*; +use futures::future::{self, Either}; //use tokio::prelude::*; //use tokio::timer::Delay; use tokio::fs::File; @@ -27,7 +27,7 @@ use tokio_codec; use hyper::http::request::Parts; use hyper::{Method, Body, Request, Response, Server, StatusCode}; use hyper::rt::{Future, Stream}; -use hyper::service::service_fn; +//use hyper::service::service_fn; use hyper::header; //use std::time::{Duration, Instant}; @@ -45,7 +45,7 @@ macro_rules! error_response { macro_rules! http_error_future { ($status:ident, $msg:expr) => {{ let resp = error_response!($status, $msg); - return Box::new(ok(resp)); + return Box::new(future::ok(resp)); }} } @@ -56,14 +56,14 @@ fn get_request_parameters_async<'a>( ) -> Box + Send + 'a> { let resp = req_body - .map_err(|err| format_err!("Promlems reading request body: {}", err)) + .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("Promlems reading request body: {}", err)))) .fold(Vec::new(), |mut acc, chunk| { if acc.len() + chunk.len() < 64*1024 { //fimxe: max request body size? acc.extend_from_slice(&*chunk); - ok(acc) - } else { - err(format_err!("Request body too large")) + future::ok(acc) } + //else { ok(acc) } //FIXMEEEEE + else { future::err(Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("Request body too large")))) } }) .and_then(move |body| { @@ -171,11 +171,11 @@ fn handle_sync_api_request<'a>( fn simple_static_file_download(filename: PathBuf) -> BoxFut { Box::new(File::open(filename) - .map_err(|err| format_err!("File open failed: {}", err)) + .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File open failed: {}", err)))) .and_then(|file| { let buf: Vec = Vec::new(); tokio::io::read_to_end(file, buf) - .map_err(|err| format_err!("File read failed: {}", err)) + .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File read failed: {}", err)))) .and_then(|data| Ok(Response::new(data.1.into()))) })) } @@ -183,7 +183,7 @@ fn simple_static_file_download(filename: PathBuf) -> BoxFut { fn chuncked_static_file_download(filename: PathBuf) -> BoxFut { Box::new(File::open(filename) - .map_err(|err| format_err!("File open failed: {}", err)) + .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File open failed: {}", err)))) .and_then(|file| { let payload = tokio_codec::FramedRead::new(file, tokio_codec::BytesCodec::new()). map(|bytes| { @@ -202,7 +202,7 @@ fn chuncked_static_file_download(filename: PathBuf) -> BoxFut { fn handle_static_file_download(filename: PathBuf) -> BoxFut { let response = tokio::fs::metadata(filename.clone()) - .map_err(|err| format_err!("File access problems: {}", err)) + .map_err(|err| Error::from(ApiError::new(StatusCode::BAD_REQUEST, format!("File access problems: {}", err)))) .and_then(|metadata| { if metadata.len() < 1024*32 { Either::A(simple_static_file_download(filename)) @@ -316,6 +316,35 @@ fn initialize_directory_aliases() -> HashMap { basedirs } +struct ApiServer { + basedir: PathBuf, +} + +impl hyper::service::Service for ApiServer { + type ReqBody = Body; + type ResBody = Body; + type Error = String; + type Future = Box, Error = String> + Send>; + + fn call(&mut self, req: Request) -> Self::Future { + + Box::new(handle_request(req).then(|result| { + match result { + Ok(res) => Ok(res), + Err(err) => { + if let Some(apierr) = err.downcast_ref::() { + let mut resp = Response::new(Body::from(apierr.message.clone())); + *resp.status_mut() = apierr.code; + Ok(resp) + } else { + Ok(error_response!(BAD_REQUEST, err.to_string())) + } + } + } + })) + } +} + lazy_static!{ static ref DIR_ALIASES: HashMap = initialize_directory_aliases(); } @@ -330,15 +359,8 @@ fn main() { let addr = ([127, 0, 0, 1], 8007).into(); let new_svc = || { - service_fn(|req| { - // clumsy way to convert failure::Error to Response - handle_request(req).then(|result| -> Result, String> { - match result { - Ok(res) => Ok(res), - Err(err) => Ok(error_response!(BAD_REQUEST, err.to_string())), - } - }) - }) + let service = ApiServer { basedir: "/var/www".into() }; + future::ok::<_,String>(service) }; let server = Server::bind(&addr)