From da0114a10e8afe9943ef84babe1d38cb98ccf831 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Wed, 21 Mar 2018 15:43:32 -0700 Subject: [PATCH] refacrtor layout --- .travis.yml | 54 ++++++++++ Cargo.toml | 3 +- src/lib.rs | 249 ++++++++++++++++++++++++++++++++++++++++++++++- src/use_prost.rs | 247 ---------------------------------------------- 4 files changed, 302 insertions(+), 251 deletions(-) create mode 100644 .travis.yml delete mode 100644 src/use_prost.rs diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..32fe873ae --- /dev/null +++ b/.travis.yml @@ -0,0 +1,54 @@ +language: rust +rust: + - 1.21.0 + - stable + - beta + - nightly + +sudo: required +dist: trusty + +env: + global: + - RUSTFLAGS="-C link-dead-code" + +addons: + apt: + packages: + - libcurl4-openssl-dev + - libelf-dev + - libdw-dev + - cmake + - gcc + - binutils-dev + - libiberty-dev + +# Add clippy +before_script: + - | + if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + ( ( cargo install clippy && export CLIPPY=true ) || export CLIPPY=false ); + fi + - export PATH=$PATH:~/.cargo/bin + +script: + - | + if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + USE_SKEPTIC=1 cargo test + else + cargo test + fi + - | + if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then + cargo clippy + fi + +# Upload docs +after_success: + - | + if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then + bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) + USE_SKEPTIC=1 cargo tarpaulin --out Xml + bash <(curl -s https://codecov.io/bash) + echo "Uploaded code coverage" + fi diff --git a/Cargo.toml b/Cargo.toml index e060b7abe..6a45a2405 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,8 @@ prost = "^0.2" http = "^0.1.5" prost-derive = "^0.2" - [workspace] members = [ "./", "examples/prost-example", -] \ No newline at end of file +] diff --git a/src/lib.rs b/src/lib.rs index 2548c3ac8..5cb2f849d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,5 +13,250 @@ extern crate prost; #[cfg(test)] #[macro_use] extern crate prost_derive; -mod use_prost; -pub use use_prost::{ ProtoBuf, ProtoBufResponseBuilder, ProtoBufHttpMessage }; \ No newline at end of file +use bytes::{Bytes, BytesMut}; +use futures::{Poll, Future, Stream}; + +use bytes::IntoBuf; +use prost::Message; +use prost::DecodeError as ProtoBufDecodeError; +use prost::EncodeError as ProtoBufEncodeError; + +use actix_web::header::http::{CONTENT_TYPE, CONTENT_LENGTH}; +use actix_web::{Responder, HttpMessage, HttpRequest, HttpResponse}; +use actix_web::dev::HttpResponseBuilder; +use actix_web::error::{Error, PayloadError, ResponseError}; +use actix_web::httpcodes::{HttpBadRequest, HttpPayloadTooLarge}; + + +#[derive(Fail, Debug)] +pub enum ProtoBufPayloadError { + /// Payload size is bigger than 256k + #[fail(display="Payload size is bigger than 256k")] + Overflow, + /// Content type error + #[fail(display="Content type error")] + ContentType, + /// Serialize error + #[fail(display="ProtoBud serialize error: {}", _0)] + Serialize(#[cause] ProtoBufEncodeError), + /// Deserialize error + #[fail(display="ProtoBud deserialize error: {}", _0)] + Deserialize(#[cause] ProtoBufDecodeError), + /// Payload error + #[fail(display="Error that occur during reading payload: {}", _0)] + Payload(#[cause] PayloadError), +} + +impl ResponseError for ProtoBufPayloadError { + + fn error_response(&self) -> HttpResponse { + match *self { + ProtoBufPayloadError::Overflow => HttpPayloadTooLarge.into(), + _ => HttpBadRequest.into(), + } + } +} + +impl From for ProtoBufPayloadError { + fn from(err: PayloadError) -> ProtoBufPayloadError { + ProtoBufPayloadError::Payload(err) + } +} + +impl From for ProtoBufPayloadError { + fn from(err: ProtoBufDecodeError) -> ProtoBufPayloadError { + ProtoBufPayloadError::Deserialize(err) + } +} + +#[derive(Debug)] +pub struct ProtoBuf(pub T); + +impl Responder for ProtoBuf { + type Item = HttpResponse; + type Error = Error; + + fn respond_to(self, _: HttpRequest) -> Result { + let mut buf = Vec::new(); + self.0.encode(&mut buf) + .map_err(|e| Error::from(ProtoBufPayloadError::Serialize(e))) + .and_then(|()| { + Ok(HttpResponse::Ok() + .content_type("application/protobuf") + .body(buf) + .into()) + }) + } +} + +pub struct ProtoBufMessage{ + limit: usize, + ct: &'static str, + req: Option, + fut: Option>>, +} + +impl ProtoBufMessage { + + /// Create `ProtoBufMessage` for request. + pub fn new(req: T) -> Self { + ProtoBufMessage{ + limit: 262_144, + req: Some(req), + fut: None, + ct: "application/protobuf", + } + } + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + /// Set allowed content type. + /// + /// By default *application/protobuf* content type is used. Set content type + /// to empty string if you want to disable content type check. + pub fn content_type(mut self, ct: &'static str) -> Self { + self.ct = ct; + self + } +} + +impl Future for ProtoBufMessage + where T: HttpMessage + Stream + 'static +{ + type Item = U; + type Error = ProtoBufPayloadError; + + fn poll(&mut self) -> Poll { + if let Some(req) = self.req.take() { + if let Some(len) = req.headers().get(CONTENT_LENGTH) { + if let Ok(s) = len.to_str() { + if let Ok(len) = s.parse::() { + if len > self.limit { + return Err(ProtoBufPayloadError::Overflow); + } + } else { + return Err(ProtoBufPayloadError::Overflow); + } + } + } + // check content-type + if !self.ct.is_empty() && req.content_type() != self.ct { + return Err(ProtoBufPayloadError::ContentType) + } + + let limit = self.limit; + let fut = req.from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + if (body.len() + chunk.len()) > limit { + Err(ProtoBufPayloadError::Overflow) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .and_then(|body| Ok(::decode(&mut body.into_buf())?)); + self.fut = Some(Box::new(fut)); + } + + self.fut.as_mut().expect("ProtoBufBody could not be used second time").poll() + } +} + + +pub trait ProtoBufResponseBuilder { + + fn protobuf(&mut self, value: T) -> Result; +} + +impl ProtoBufResponseBuilder for HttpResponseBuilder { + + fn protobuf(&mut self, value: T) -> Result { + self.header(CONTENT_TYPE, "application/protobuf"); + + let mut body = Vec::new(); + value.encode(&mut body).map_err(ProtoBufPayloadError::Serialize)?; + Ok(self.body(body)?) + } +} + + + +pub trait ProtoBufHttpMessage { + fn protobuf(self) -> ProtoBufMessage + where Self: Stream + Sized; +} + +impl ProtoBufHttpMessage for HttpRequest { + + #[inline] + fn protobuf(self) -> ProtoBufMessage + where Self: Stream + Sized + { + ProtoBufMessage::new(self) + } +} + + + +#[cfg(test)] +mod tests { + use super::*; + use http::header; + + impl PartialEq for ProtoBufPayloadError { + fn eq(&self, other: &ProtoBufPayloadError) -> bool { + match *self { + ProtoBufPayloadError::Overflow => match *other { + ProtoBufPayloadError::Overflow => true, + _ => false, + }, + ProtoBufPayloadError::ContentType => match *other { + ProtoBufPayloadError::ContentType => true, + _ => false, + }, + _ => false, + } + } + } + + + #[derive(Clone, Debug, PartialEq, Message)] + pub struct MyObject { + #[prost(int32, tag="1")] + pub number: i32, + #[prost(string, tag="2")] + pub name: String, + } + + #[test] + fn test_protobuf() { + let protobuf = ProtoBuf(MyObject{number: 9 , name: "test".to_owned()}); + let resp = protobuf.respond_to(HttpRequest::default()).unwrap(); + assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/protobuf"); + } + + #[test] + fn test_protobuf_message() { + let req = HttpRequest::default(); + let mut protobuf = req.protobuf::(); + assert_eq!(protobuf.poll().err().unwrap(), ProtoBufPayloadError::ContentType); + + let mut req = HttpRequest::default(); + req.headers_mut().insert(header::CONTENT_TYPE, + header::HeaderValue::from_static("application/protobuf")); + let mut protobuf = req.protobuf::().content_type("text/protobuf"); + assert_eq!(protobuf.poll().err().unwrap(), ProtoBufPayloadError::ContentType); + + let mut req = HttpRequest::default(); + req.headers_mut().insert(header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json")); + req.headers_mut().insert(header::CONTENT_LENGTH, + header::HeaderValue::from_static("10000")); + let mut protobuf = req.protobuf::().limit(100); + assert_eq!(protobuf.poll().err().unwrap(), ProtoBufPayloadError::Overflow); + } +} diff --git a/src/use_prost.rs b/src/use_prost.rs deleted file mode 100644 index ba3a26867..000000000 --- a/src/use_prost.rs +++ /dev/null @@ -1,247 +0,0 @@ -use bytes::{Bytes, BytesMut}; -use futures::{Poll, Future, Stream}; - -use bytes::IntoBuf; -use prost::Message; -use prost::DecodeError as ProtoBufDecodeError; -use prost::EncodeError as ProtoBufEncodeError; - -use actix_web::header::http::{CONTENT_TYPE, CONTENT_LENGTH}; -use actix_web::{Responder, HttpMessage, HttpRequest, HttpResponse}; -use actix_web::dev::HttpResponseBuilder; -use actix_web::error::{Error, PayloadError, ResponseError}; -use actix_web::httpcodes::{HttpBadRequest, HttpPayloadTooLarge}; - - -#[derive(Fail, Debug)] -pub enum ProtoBufPayloadError { - /// Payload size is bigger than 256k - #[fail(display="Payload size is bigger than 256k")] - Overflow, - /// Content type error - #[fail(display="Content type error")] - ContentType, - /// Serialize error - #[fail(display="ProtoBud serialize error: {}", _0)] - Serialize(#[cause] ProtoBufEncodeError), - /// Deserialize error - #[fail(display="ProtoBud deserialize error: {}", _0)] - Deserialize(#[cause] ProtoBufDecodeError), - /// Payload error - #[fail(display="Error that occur during reading payload: {}", _0)] - Payload(#[cause] PayloadError), -} - -impl ResponseError for ProtoBufPayloadError { - - fn error_response(&self) -> HttpResponse { - match *self { - ProtoBufPayloadError::Overflow => HttpPayloadTooLarge.into(), - _ => HttpBadRequest.into(), - } - } -} - -impl From for ProtoBufPayloadError { - fn from(err: PayloadError) -> ProtoBufPayloadError { - ProtoBufPayloadError::Payload(err) - } -} - -impl From for ProtoBufPayloadError { - fn from(err: ProtoBufDecodeError) -> ProtoBufPayloadError { - ProtoBufPayloadError::Deserialize(err) - } -} - -#[derive(Debug)] -pub struct ProtoBuf(pub T); - -impl Responder for ProtoBuf { - type Item = HttpResponse; - type Error = Error; - - fn respond_to(self, _: HttpRequest) -> Result { - let mut buf = Vec::new(); - self.0.encode(&mut buf) - .map_err(|e| Error::from(ProtoBufPayloadError::Serialize(e))) - .and_then(|()| { - Ok(HttpResponse::Ok() - .content_type("application/protobuf") - .body(buf) - .into()) - }) - } -} - -pub struct ProtoBufMessage{ - limit: usize, - ct: &'static str, - req: Option, - fut: Option>>, -} - -impl ProtoBufMessage { - - /// Create `ProtoBufMessage` for request. - pub fn new(req: T) -> Self { - ProtoBufMessage{ - limit: 262_144, - req: Some(req), - fut: None, - ct: "application/protobuf", - } - } - - /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self - } - - /// Set allowed content type. - /// - /// By default *application/protobuf* content type is used. Set content type - /// to empty string if you want to disable content type check. - pub fn content_type(mut self, ct: &'static str) -> Self { - self.ct = ct; - self - } -} - -impl Future for ProtoBufMessage - where T: HttpMessage + Stream + 'static -{ - type Item = U; - type Error = ProtoBufPayloadError; - - fn poll(&mut self) -> Poll { - if let Some(req) = self.req.take() { - if let Some(len) = req.headers().get(CONTENT_LENGTH) { - if let Ok(s) = len.to_str() { - if let Ok(len) = s.parse::() { - if len > self.limit { - return Err(ProtoBufPayloadError::Overflow); - } - } else { - return Err(ProtoBufPayloadError::Overflow); - } - } - } - // check content-type - if !self.ct.is_empty() && req.content_type() != self.ct { - return Err(ProtoBufPayloadError::ContentType) - } - - let limit = self.limit; - let fut = req.from_err() - .fold(BytesMut::new(), move |mut body, chunk| { - if (body.len() + chunk.len()) > limit { - Err(ProtoBufPayloadError::Overflow) - } else { - body.extend_from_slice(&chunk); - Ok(body) - } - }) - .and_then(|body| Ok(::decode(&mut body.into_buf())?)); - self.fut = Some(Box::new(fut)); - } - - self.fut.as_mut().expect("ProtoBufBody could not be used second time").poll() - } -} - - -pub trait ProtoBufResponseBuilder { - - fn protobuf(&mut self, value: T) -> Result; -} - -impl ProtoBufResponseBuilder for HttpResponseBuilder { - - fn protobuf(&mut self, value: T) -> Result { - self.header(CONTENT_TYPE, "application/protobuf"); - - let mut body = Vec::new(); - value.encode(&mut body).map_err(|e| ProtoBufPayloadError::Serialize(e))?; - Ok(self.body(body)?) - } -} - - - -pub trait ProtoBufHttpMessage { - fn protobuf(self) -> ProtoBufMessage - where Self: Stream + Sized; -} - -impl ProtoBufHttpMessage for HttpRequest { - - #[inline] - fn protobuf(self) -> ProtoBufMessage - where Self: Stream + Sized - { - ProtoBufMessage::new(self) - } -} - - - -#[cfg(test)] -mod tests { - use super::*; - use http::header; - - impl PartialEq for ProtoBufPayloadError { - fn eq(&self, other: &ProtoBufPayloadError) -> bool { - match *self { - ProtoBufPayloadError::Overflow => match *other { - ProtoBufPayloadError::Overflow => true, - _ => false, - }, - ProtoBufPayloadError::ContentType => match *other { - ProtoBufPayloadError::ContentType => true, - _ => false, - }, - _ => false, - } - } - } - - - #[derive(Clone, Debug, PartialEq, Message)] - pub struct MyObject { - #[prost(int32, tag="1")] - pub number: i32, - #[prost(string, tag="2")] - pub name: String, - } - - #[test] - fn test_protobuf() { - let protobuf = ProtoBuf(MyObject{number: 9 , name: "test".to_owned()}); - let resp = protobuf.respond_to(HttpRequest::default()).unwrap(); - assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/protobuf"); - } - - #[test] - fn test_protobuf_message() { - let req = HttpRequest::default(); - let mut protobuf = req.protobuf::(); - assert_eq!(protobuf.poll().err().unwrap(), ProtoBufPayloadError::ContentType); - - let mut req = HttpRequest::default(); - req.headers_mut().insert(header::CONTENT_TYPE, - header::HeaderValue::from_static("application/protobuf")); - let mut protobuf = req.protobuf::().content_type("text/protobuf"); - assert_eq!(protobuf.poll().err().unwrap(), ProtoBufPayloadError::ContentType); - - let mut req = HttpRequest::default(); - req.headers_mut().insert(header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json")); - req.headers_mut().insert(header::CONTENT_LENGTH, - header::HeaderValue::from_static("10000")); - let mut protobuf = req.protobuf::().limit(100); - assert_eq!(protobuf.poll().err().unwrap(), ProtoBufPayloadError::Overflow); - } -} \ No newline at end of file