-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add some more utility methods to RequestExt #13
Comments
Here are some utility methods for use std::fmt::Display;
use std::str::FromStr;
use hyper::{Body, Request, Response, StatusCode};
use routerify::prelude::RequestExt;
use routerify_query::RequestQueryExt;
use serde::de::DeserializeOwned;
use error::HandlerError;
#[derive(Clone, Debug, thiserror::Error)]
#[async_trait::async_trait]
trait RouterRequestExt {
/// Extract a query parameter from the request URI, and error if not provided.
fn required_query(&self, name: &'static str) -> Result<&str, HandlerError>;
/// Extract a required query parameter from the request URI, and fallibly parse
/// the value into a specified type.
fn required_query_as<T>(&self, name: &'static str) -> Result<T, HandlerError>
where
T: FromStr,
<T as FromStr>::Err: Display;
/// Extract an optional query parameter from the request URI.
fn optional_query(&self, name: &'static str) -> Option<&str>;
/// Extract a query parameter from the request URI, and fallibly parse the
/// value into a specified type.
fn optional_query_as<T>(&self, name: &'static str) -> Result<Option<T>, HandlerError>
where
T: FromStr,
<T as FromStr>::Err: Display;
/// Extract a URL parameter from the request URI, and error if not provided.
fn required_param(&self, name: &'static str) -> Result<&str, HandlerError>;
/// Extract a required URL parameter from the request URI, and attempt to
/// parse the value into a generic type.
fn required_param_as<T>(&self, name: &'static str) -> Result<T, HandlerError>
where
T: FromStr,
<T as FromStr>::Err: Display;
/// Extract an optional URL parameter from the request URI.
fn optional_param(&self, name: &'static str) -> Option<&str>;
/// Extract a URL parameter from the request URI, and fallibly parse the
/// value into a specified type.
fn optional_param_as<T>(&self, name: &'static str) -> Result<Option<T>, HandlerError>
where
T: FromStr,
<T as FromStr>::Err: Display;
/// Retrieve an already initialized singleton containing scope state, and
/// error if a singleton with the specified typeid does not exist.
fn state<S: Send + Sync + 'static>(&self) -> Result<&S, HandlerError>;
/// Attempt to deserialize the body of the request into a specific type.
async fn request_body<T: DeserializeOwned>(&mut self) -> Result<T, HandlerError>;
/// Retrieve a non-empty string containing the value of the bearer token.
///
/// This will error when:
/// 1. The "Authorization" header is not supplied.
/// 2. The authentication type is not "Bearer".
/// 2. The authentication credentials is empty.
async fn bearer_token(&self) -> Result<&str, HandlerError>;
} They work to simplify common use-cases for parsing request input data and appropriate handling errors throughout, rather than /// An endpoint for authorized users to create a new product for a certain category.
async fn create_product(mut req: Request<Body>) -> Result<Response<Body>, HandlerError> {
#[derive(Deserialize, Debug)]
struct Request {
name: String,
image_url: String,
category_id: String,
}
#[derive(Serialize)]
struct Response {
product_id: String,
}
let access_token = req.bearer_token().await?;
let body = req.request_body::<Request>().await?;
let state = req.state::<State>()?;
let command = products::commands::CreateProduct {
name: body.name,
image_url: body.image_url,
category_id: body.category_id,
access_token,
};
match state.write_service.lock().await.create_product(command).await {
Ok(product_id) => {
let res = serde_json::to_string(&Response { product_id }).unwrap();
Ok(created(res)) // Helper function to create 201 CREATED
}
Err(e) => Err(e.into()), // ApplicationError into HandlerError
}
}
/// An endpoint for authorized users to search for products in a certain category.
///
/// The length of the results can be configured.
async fn find_products(req: Request<Body>) -> Result<Response<Body>, HandlerError> {
#[derive(Serialize)]
struct Response {
products: Vec<products::ProductReadModel>,
}
let access_token = req.bearer_token().await?;
let category_id = req.required_param("category_id")?;
let product_search_query = req.required_query("search_query")?;
let product_list_limit = req.optional_query_as::<i64>("limit")?;
let query = products::queries::FindProducts {
store_id,
product_search_query,
product_list_limit,
access_token,
};
let state = req.state::<State>()?;
match state.read_service.find_products(query).await {
Ok(products) => {
let resp = ok(serde_json::to_string(&Response { products }).unwrap()); // Helper function to create 201 CREATED
Ok(resp)
}
Err(e) => return Err(e.into()), // ApplicationError into HandlerError
}
} I can provide the #[derive(Clone, Debug, Error)]
pub enum HandlerError {
// 400
#[error("A required URL argument was not specified for this request: {name}")]
MissingRequiredUrlArgument { name: &'static str },
#[error("A URL argument was not specified with the correct type: {name} failed with {cause}")]
InvalidUrlArgumentArgumentType { name: &'static str, cause: String },
#[error("A required query parameter was not specified for this request: {name}")]
MissingRequiredQueryParameter { name: &'static str },
#[error("A query parameter was not specified with the correct type: {name} failed with {cause}")]
InvalidQueryParameterType { name: &'static str, cause: String },
#[error("The body of the request is invalid: {cause}")]
InvalidBody { cause: String },
#[error("A required HTTP header was not specified: {name}")]
MissingRequiredHeader { name: &'static str },
#[error("The value provided for one of the HTTP headers was not in the correct format: {name}")]
InvalidHeaderValue { name: &'static str },
#[error("The request was rejected due to bad input from the client: {cause}")]
BadRequest { cause: String },
// 401
#[error("Server failed to authenticate the request.")]
Unauthorized { cause: String },
// 403
#[error("The agent does not have sufficient permissions to execute this operation.")]
Forbidden { cause: String },
// 500
#[error("The server encountered an internal error. Please retry the request.")]
InternalServerError { cause: String },
#[error("The server encountered an internal error. Please retry the request.")]
RouterMissingHandlerState,
} |
Reference methods: https://docs.rs/reqwest/0.10.4/reqwest/struct.Response.html#method.cookies
including
.cookies()
.The text was updated successfully, but these errors were encountered: