//! For either helper, see [`Either`]. use bytes::Bytes; use crate::{ body::EitherBody, dev, web::{Form, Json}, Error, FromRequest, HttpRequest, HttpResponse, Responder, }; /// Combines two extractor or responder types into a single type. /// /// # Extractor /// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for /// "polymorphic payloads" where, for example, a form might be JSON or URL encoded. /// /// It is important to note that this extractor, by necessity, buffers the entire request payload /// as part of its implementation. Though, it does respect any `PayloadConfig` maximum size limits. /// /// ``` /// use actix_web::{post, web, Either}; /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { /// name: String, /// } /// /// // handler that accepts form as JSON or form-urlencoded. /// #[post("/")] /// async fn index(form: Either, web::Form>) -> String { /// let name: String = match form { /// Either::Left(json) => json.name.to_owned(), /// Either::Right(form) => form.name.to_owned(), /// }; /// /// format!("Welcome {}!", name) /// } /// ``` /// /// # Responder /// It may be desirable to use a concrete type for a response with multiple branches. As long as /// both types implement `Responder`, so will the `Either` type, enabling it to be used as a /// handler's return type. /// /// All properties of a response are determined by the Responder branch returned. /// /// ``` /// use actix_web::{get, Either, Error, HttpResponse}; /// /// #[get("/")] /// async fn index() -> Either<&'static str, Result> { /// if 1 == 2 { /// // respond with Left variant /// Either::Left("Bad data") /// } else { /// // respond with Right variant /// Either::Right( /// Ok(HttpResponse::Ok() /// .content_type(mime::TEXT_HTML) /// .body("

Hello!

")) /// ) /// } /// } /// ``` #[derive(Debug, PartialEq, Eq)] pub enum Either { /// A value of type `L`. Left(L), /// A value of type `R`. Right(R), } impl Either, Json> { pub fn into_inner(self) -> T { match self { Either::Left(form) => form.into_inner(), Either::Right(form) => form.into_inner(), } } } impl Either, Form> { pub fn into_inner(self) -> T { match self { Either::Left(form) => form.into_inner(), Either::Right(form) => form.into_inner(), } } } #[cfg(test)] impl Either { pub(self) fn unwrap_left(self) -> L { match self { Either::Left(data) => data, Either::Right(_) => { panic!("Cannot unwrap Left branch. Either contains an `R` type.") } } } pub(self) fn unwrap_right(self) -> R { match self { Either::Left(_) => { panic!("Cannot unwrap Right branch. Either contains an `L` type.") } Either::Right(data) => data, } } } /// See [here](#responder) for example of usage as a handler return type. impl Responder for Either where L: Responder, R: Responder, { type Body = EitherBody; fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { Either::Left(a) => a.respond_to(req).map_into_left_body(), Either::Right(b) => b.respond_to(req).map_into_right_body(), } } } /// A composite error resulting from failure to extract an `Either`. /// /// The implementation of `Into` will return the payload buffering error or the /// error from the primary extractor. To access the fallback error, use a match clause. #[derive(Debug)] pub enum EitherExtractError { /// Error from payload buffering, such as exceeding payload max size limit. Bytes(Error), /// Error from primary and fallback extractors. Extract(L, R), } impl From> for Error where L: Into, R: Into, { fn from(err: EitherExtractError) -> Error { match err { EitherExtractError::Bytes(err) => err, EitherExtractError::Extract(l_err, _r_err) => l_err.into(), } } } /// See [here](#extractor) for example of usage as an extractor. impl FromRequest for Either where L: FromRequest + 'static, R: FromRequest + 'static, { type Error = EitherExtractError; async fn from_request( req: &HttpRequest, payload: &mut dev::Payload, ) -> Result { let buf = Bytes::from_request(req, payload) .await .map_err(EitherExtractError::Bytes)?; match L::from_request(req, &mut payload_from_bytes(buf.clone())).await { Ok(left) => Ok(Either::Left(left)), Err(l_err) => match R::from_request(req, &mut payload_from_bytes(buf)).await { Ok(right) => Ok(Either::Right(right)), Err(r_err) => Err(EitherExtractError::Extract(l_err, r_err)), }, } } } fn payload_from_bytes(bytes: Bytes) -> dev::Payload { let (_, mut h1_payload) = actix_http::h1::Payload::create(true); h1_payload.unread_data(bytes); dev::Payload::from(h1_payload) } #[cfg(test)] mod tests { use serde::{Deserialize, Serialize}; use super::*; use crate::{ test::TestRequest, web::{Form, Json}, }; #[derive(Debug, Clone, Serialize, Deserialize)] struct TestForm { hello: String, } #[actix_rt::test] async fn test_either_extract_first_try() { let (req, mut pl) = TestRequest::default() .set_form(TestForm { hello: "world".to_owned(), }) .to_http_parts(); let form = Either::, Json>::from_request(&req, &mut pl) .await .unwrap() .unwrap_left() .into_inner(); assert_eq!(&form.hello, "world"); } #[actix_rt::test] async fn test_either_extract_fallback() { let (req, mut pl) = TestRequest::default() .set_json(TestForm { hello: "world".to_owned(), }) .to_http_parts(); let form = Either::, Json>::from_request(&req, &mut pl) .await .unwrap() .unwrap_right() .into_inner(); assert_eq!(&form.hello, "world"); } #[actix_rt::test] async fn test_either_extract_recursive_fallback() { let (req, mut pl) = TestRequest::default() .set_payload(Bytes::from_static(b"!@$%^&*()")) .to_http_parts(); let payload = Either::, Json>, Bytes>::from_request(&req, &mut pl) .await .unwrap() .unwrap_right(); assert_eq!(&payload.as_ref(), &b"!@$%^&*()"); } #[actix_rt::test] async fn test_either_extract_recursive_fallback_inner() { let (req, mut pl) = TestRequest::default() .set_json(TestForm { hello: "world".to_owned(), }) .to_http_parts(); let form = Either::, Json>, Bytes>::from_request(&req, &mut pl) .await .unwrap() .unwrap_left() .unwrap_right() .into_inner(); assert_eq!(&form.hello, "world"); } }