I am attempting to work with nested match arms, both handling the Err case with the same code.
This seems quite repetitive, and if there were further parsing and unwrapping to be done it would get even worse.
Is there a way to let the Err case from the inner arm "bubble up" to the outer one, or otherwise apply the same error handling to those multiple different, possibly nested Err cases?
pub fn get_content_length(
headers: axum::http::HeaderMap,
) -> Result<usize, (axum::http::StatusCode, String)> {
let content_length = match headers.get("content-length") {
Some(header_val) => match header_val.to_str() {
Ok(header_val) => match header_val.parse::<usize>() {
Ok(content_length) => content_length,
Err(e) => {
return Err((
axum::http::StatusCode::BAD_REQUEST,
format!("error parsing header content-length: {}", e.to_string()),
))
}
},
Err(e) => {
return Err((
axum::http::StatusCode::BAD_REQUEST,
format!("error parsing header content-length: {}", e.to_string()),
))
}
},
None => {
return Err((
axum::http::StatusCode::BAD_REQUEST,
String::from("missing content-length header"),
))
}
};
Ok(content_length)
}
The errors are of different type, so you cannot do that. But since all you want is to display them, you can convert them to strings:
pub fn get_content_length(
headers: axum::http::HeaderMap,
) -> Result<usize, (axum::http::StatusCode, String)> {
let content_length = match headers.get("content-length") {
Some(header_val) => match header_val
.to_str()
.map_err(|e| e.to_string())
.and_then(|header_val| header_val.parse::<usize>().map_err(|e| e.to_string()))
{
Ok(content_length) => content_length,
Err(e) => {
return Err((
axum::http::StatusCode::BAD_REQUEST,
format!("error parsing header content-length: {}", e.to_string()),
))
}
},
None => {
return Err((
axum::http::StatusCode::BAD_REQUEST,
String::from("missing content-length header"),
))
}
};
Ok(content_length)
}
You can simplify your code fith if-let or let-else constructions.
Your example is too big for me to show the use of constructs on it, sorry. In the future for MRE use playground.
pub fn get_content_length_ref(
headers: axum::http::HeaderMap,
) -> Result<usize, (axum::http::StatusCode, String)> {
let Some(header_val) = headers.get("content-length") else {
return Err((
axum::http::StatusCode::BAD_REQUEST,
"missing content-length header".to_owned(),
));
};
let Ok(header_str) = header_val.to_str() else {
return Err((
axum::http::StatusCode::BAD_REQUEST,
"error parsing header to str".to_owned(),
));
};
let Ok(header_val) = header_str.parse::<usize>() else {
return Err((
axum::http::StatusCode::BAD_REQUEST,
"error parsing header to int".to_owned(),
));
};
Ok(header_val)
}
But simplest way for errors - anyhow crate.
use anyhow::Context;
pub fn get_content_length(headers: axum::http::HeaderMap) -> anyhow::Result<usize> {
let header = headers
.get("content-length")
.context("missing content-length header")?;
let header = header.to_str()?;
Ok(header.parse::<usize>()?)
}
Related
I am working on a large project in Rust with many moving pieces. One of my agendas is to log an error if something goes wrong in any part so I can debug later.
However, it seems for every function call the conciseness has vanished, plausibly due to my naivete.
How can I make this more concise? I looked at this question which essentially asks to use a match to capture both OK() and Err.
fn pathway_1(x: &str, y: &str) -> Option<Vec<String>> {
let unique_numbers = match query_unique_numbers(&x, &y) {
Ok(r) => r
Err(e) => {
log::error!(target: "normal", "Could not query unique numbers. Err {e}");
return None;
}
}
let unique_people = match query_unique_people(&unique_numbers, &y) {
Ok(r) => r
Err(e) => {
log::error!(target: "normal", "Could not query unique people. Err {e}");
return None;
}
}
let relevant_things_by_people = match query_relevant_things(&unique_people, &y) {
Ok(r) => r
Err(e) => {
log::error!(target: "normal", "Could not query relevant things. Err {e}");
return None;
}
}
/// Many such function calls below.
Some(vector_of_strings)
}
You can use .ok() to convert a Result into an Option and then use ? to propagate it.
Logging on each result could be achieved with .map_err() to apply a "transformation" to the error. It may be slightly better to use .inspect_err() (when stabilized) or .tap_err() (from the tap crate), though since the error is not used after logging, it doesn't really matter:
fn pathway_1(x: &str, y: &str) -> Option<Vec<String>> {
let unique_numbers = query_unique_numbers(&x, &y)
.map_err(|e| { log::error!(target: "normal", "Could not query unique numbers. Err {e}"); })
.ok()?;
let unique_people = query_unique_people(&unique_numbers, &y)
.map_err(|e| { log::error!(target: "normal", "Could not query unique people. Err {e}"); })
.ok()?;
let relevant_things_by_people = query_relevant_things(&unique_people, &y)
.map_err(|e| { log::error!(target: "normal", "Could not query relevant things. Err {e}"); })
.ok()?;
/// Many such function calls below.
Some(vector_of_strings)
}
However, I would not do it like this. You seem to be using Option in the return type to indicate failure. If that is the case, you should return a Result. And if you do that, you can create a proper encompassing error type that can express what step failed, with what original error, and the display logic (using something like the thiserror crate):
use thiserror::Error;
#[derive(Error, Debug)]
enum QueryError {
#[error("Could not query unique numbers. Err {0}")]
UniqueNumbers(SomeError),
#[error("Could not query unique people. Err {0}")]
UniquePeople(SomeError),
#[error("Could not query relevant things. Err {0}")]
RelevantThings(SomeError),
}
fn pathway_1(x: &str, y: &str) -> Result<Vec<String>, QueryError> {
let unique_numbers = query_unique_numbers(&x, &y)
.map_err(QueryError::UniqueNumbers)?;
let unique_people = query_unique_people(&unique_numbers, &y)
.map_err(QueryError::UniquePeople)?;
let relevant_things_by_people = query_relevant_things(&unique_people, &y)
.map_err(QueryError::RelevantThings)?;
/// Many such function calls below.
Ok(vector_of_strings)
}
Then you can handle all errors in one place in the caller. For example:
match pathway_1(x, y) {
Ok(vector_of_strings) => {
// do something
}
Err(e) => {
log::error!(target: "normal", "{e}");
}
}
You can create a custom method on Result, log(), that logs the error with context:
trait ResultExt<T> {
fn log(self, context: &str) -> Option<T>;
}
impl<T, E: std::fmt::Display> ResultExt<T> for Result<T, E> {
fn log(self, context: &str) -> Option<T> {
match self {
Ok(v) => Some(v),
Err(err) => {
log::error!(target: "normal", "{context}. Err {err}");
None
}
}
}
}
fn pathway_1(x: &str, y: &str) -> Option<Vec<String>> {
let unique_numbers = query_unique_numbers(&x, &y).log("Could not query unique numbers")?;
let unique_people =
query_unique_people(&unique_numbers, &y).log("Could not query unique people")?;
let relevant_things_by_people =
query_relevant_things(&unique_people, &y).log("Could not query relevant things")?;
/// Many such function calls below.
Some(vector_of_strings)
}
I'm new to Rust and am trying to wrap my head around error handling.
I'm trying to return error if parsing the date goes wrong, here is the function:
pub fn create_posts(contents: &Vec<String>) -> Result<Vec<Post>, CreatePostError> {
const TITLE_SEP: &str = "Title: ";
const DESC_SEP: &str = "Description: ";
const DATE_SEP: &str = "Date: ";
const TAGS_SEP: &str = "Tags: ";
let mut posts: Vec<Post> = Vec::new();
for entry in contents {
let lines = entry.lines().collect::<Vec<_>>();
let metadata = lines[0].contains(&TITLE_SEP)
&& lines[1].contains(&DESC_SEP)
&& lines[2].contains(&DATE_SEP)
&& lines[3].contains(&TAGS_SEP);
if metadata {
let date = &lines[2][DATE_SEP.len()..];
let parsed_date = match NaiveDate::parse_from_str(date, "%Y-%m-%d") {
Ok(parsed_date) => parsed_date,
Err(e) => eprintln!("Error: {:?}", CreatePostError::ParseError { inner_err: e }),
};
let tags: Vec<String> = lines[3][TAGS_SEP.len()..]
.split(", ")
.map(|s| s.to_string())
.collect();
let mut article_content = String::new();
for line in &lines[4..] {
article_content.push_str(line);
article_content.push_str("\n")
}
let post = Post {
title: lines[0][TITLE_SEP.len()..].to_string(),
description: lines[1][DESC_SEP.len()..].to_string(),
date: parsed_date,
tags,
content: article_content,
};
posts.push(post);
} else {
return Err(CreatePostError::MetadataError);
}
}
return Ok(posts);
}
You can see the full code here since i wrote custom errors: link
The problem I'm having is with this part:
let date = &lines[2][DATE_SEP.len()..];
let parsed_date = match NaiveDate::parse_from_str(date, "%Y-%m-%d") {
Ok(parsed_date) => parsed_date,
Err(e) => eprintln!("Error: {:?}", CreatePostError::ParseError { inner_err: e }),
};
I'm getting match arms have incompatible types. Expected struct NaiveDate, found ()
Here is my enum and impl for the error:
#[derive(Debug)]
pub enum CreatePostError {
ReadFileError { path: PathBuf },
MetadataError,
ParseError { inner_err: ParseError },
}
impl fmt::Display for CreatePostError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::ReadFileError { path } => write!(f, "Error reading file {path:?}"),
Self::MetadataError => write!(f, "Some metadata is missing"),
Self::ParseError { inner_err } => {
write!(f, "Error parsing date: {inner_err}")
}
}
}
}
impl From<chrono::format::ParseError> for CreatePostError {
fn from(e: chrono::format::ParseError) -> Self {
CreatePostError::ParseError { inner_err: e }
}
}
You probably want to have a result here, here is a way to do it:
let parsed_date = match NaiveDate::parse_from_str(date, "%Y-%m-%d") {
Ok(parsed_date) => Ok(parsed_date),
Err(e) => {eprintln!("Error: {:?}", &e);
Err(CreatePostError::ParseError { inner_err: e }}),
}?;
The ? is saying: if the thing before is an error, return it, and if it is Ok, unwrap it.
This pattern is so common that rust's result gives a lot of utilities to make this kind of things easier. Here, the map_err function would make it more straightforward: map_err
see:
let parsed_date = NaiveDate::parse_from_str(date, "%Y-%m-%d")
.map_err(|e| {
eprintln!("Error: {:?}", &e);
CreatePostError::ParseError { inner_err: e }})?;
But it is only a matter of preference and it might be a lot to digest if you are just beginning, so you can choose the way that you like the most.
I'd like to write a very simple middleware using actix_web framework but it's so far beating me on every front.
I have a skeleton like this:
let result = actix_web::HttpServer::new(move || {
actix_web::App::new()
.wrap_fn(move |req, srv| {
srv.call(req).map(move|res| {
println!("Got response");
// let s = res.unwrap().response().body();
// ???
res
})
})
})
.bind("0.0.0.0:8080")?
.run()
.await;
and I can access ResponseBody type via res.unwrap().response().body() but I don't know what can I do with this.
Any ideas?
This is an example of how I was able to accomplish this with 4.0.0-beta.14:
use std::cell::RefCell;
use std::pin::Pin;
use std::rc::Rc;
use std::collections::HashMap;
use std::str;
use erp_contrib::{actix_http, actix_web, futures, serde_json};
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::{HttpMessage, body, http::StatusCode, error::Error ,HttpResponseBuilder};
use actix_http::{h1::Payload, header};
use actix_web::web::{BytesMut};
use futures::future::{ok, Future, Ready};
use futures::task::{Context, Poll};
use futures::StreamExt;
use crate::response::ErrorResponse;
pub struct UnhandledErrorResponse;
impl<S: 'static> Transform<S, ServiceRequest> for UnhandledErrorResponse
where
S: Service<ServiceRequest, Response = ServiceResponse, Error = Error>,
S::Future: 'static,
{
type Response = ServiceResponse;
type Error = Error;
type Transform = UnhandledErrorResponseMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(UnhandledErrorResponseMiddleware { service: Rc::new(RefCell::new(service)), })
}
}
pub struct UnhandledErrorResponseMiddleware<S> {
service: Rc<RefCell<S>>,
}
impl<S> Service<ServiceRequest> for UnhandledErrorResponseMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse, Error = Error> + 'static,
S::Future: 'static,
{
type Response = ServiceResponse;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&self, mut req: ServiceRequest) -> Self::Future {
let svc = self.service.clone();
Box::pin(async move {
/* EXTRACT THE BODY OF REQUEST */
let mut request_body = BytesMut::new();
while let Some(chunk) = req.take_payload().next().await {
request_body.extend_from_slice(&chunk?);
}
let mut orig_payload = Payload::empty();
orig_payload.unread_data(request_body.freeze());
req.set_payload(actix_http::Payload::from(orig_payload));
/* now process the response */
let res: ServiceResponse = svc.call(req).await?;
let content_type = match res.headers().get("content-type") {
None => { "unknown"}
Some(header) => {
match header.to_str() {
Ok(value) => {value}
Err(_) => { "unknown"}
}
}
};
return match res.response().error() {
None => {
Ok(res)
}
Some(error) => {
if content_type.to_uppercase().contains("APPLICATION/JSON") {
Ok(res)
} else {
let error = error.to_string();
let new_request = res.request().clone();
/* EXTRACT THE BODY OF RESPONSE */
let _body_data =
match str::from_utf8(&body::to_bytes(res.into_body()).await?){
Ok(str) => {
str
}
Err(_) => {
"Unknown"
}
};
let mut errors = HashMap::new();
errors.insert("general".to_string(), vec![error]);
let new_response = match ErrorResponse::new(&false, errors) {
Ok(response) => {
HttpResponseBuilder::new(StatusCode::BAD_REQUEST)
.insert_header((header::CONTENT_TYPE, "application/json"))
.body(serde_json::to_string(&response).unwrap())
}
Err(_error) => {
HttpResponseBuilder::new(StatusCode::BAD_REQUEST)
.insert_header((header::CONTENT_TYPE, "application/json"))
.body("An unknown error occurred.")
}
};
Ok(ServiceResponse::new(
new_request,
new_response
))
}
}
}
})
}
}
The extraction of the Request Body is straightforward and similar to how Actix example's illustrate. However, with the update to version Beta 14, pulling the bytes directly from AnyBody has changed with the introduction of BoxedBody. Fortunately, I found a utility function body::to_bytes (use actix_web::body::to_bytes) which does a good job. It's current implementation looks like this:
pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
let cap = match body.size() {
BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()),
BodySize::Sized(size) => size as usize,
// good enough first guess for chunk size
BodySize::Stream => 32_768,
};
let mut buf = BytesMut::with_capacity(cap);
pin!(body);
poll_fn(|cx| loop {
let body = body.as_mut();
match ready!(body.poll_next(cx)) {
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
None => return Poll::Ready(Ok(())),
Some(Err(err)) => return Poll::Ready(Err(err)),
}
})
.await?;
Ok(buf.freeze())
}
which I believe should be fine to extract the body in this way, as the body is extracted from the body stream by to_bytes().
If someone has a better way, let me know, but it was a little bit of pain, and I only had recently determined how to do it in Beta 13 when it switched to Beta 14.
This particular example intercepts errors and rewrites them to JSON format if they're not already json format. This would be the case, as an example, if an error occurs outside of a handler, such as parsing JSON in the handler function itself _data: web::Json<Request<'a, LoginRequest>> and not in the handler body. Extracting the Request Body and Response Body is not necessary to accomplish the goal, and is just here for illustration.
So I have the following code that sends multithreaded requests to a list of domains. So far I am able to grab individual data from the response, such as the bytes, or the url, or the status code, etc. but not all of them together. I would like to store all of these values into vectors so it can be written to a file. I know this is probably a super dumb question, but i've been working on it for days and can't figure it out. Any help is appreciated!
#[tokio::main]
async fn get_request(urls: Vec<&str>, paths: Vec<&str>) {
let client = Client::new();
for path in paths {
let urls = urls.clone();
let bodies = stream::iter(urls)
.map(|url| {
let client = &client;
async move {
let mut full_url = String::new();
full_url.push_str(url);
full_url.push_str(path);
let resp = client
.get(&full_url)
.timeout(Duration::from_secs(3))
.send()
.await;
resp
}
})
.buffer_unordered(PARALLEL_REQUESTS);
bodies
.for_each(|b| async {
match b {
Ok(b) => {
let mut body_test = &b.bytes().await;
match body_test {
Ok(body_test) => {
let mut stringed_bytes = str::from_utf8(&body_test);
match stringed_bytes {
Ok(stringed_bytes) => {
println!("stringed bytes: {}", stringed_bytes);
}
Err(e) => println!("Stringified Error: {}", e),
}
}
Err(e) => println!("body Error: {}", e),
}
}
Err(e) => println!("Got an error: {}", e),
}
})
.await;
}
}
I would try creating a struct that would store your desired outputs that you would like to acquire from the body or the hyper::Response. For example:
struct DesiredContent {
code: Option<u16>,
body: Json
...
}
And then implementing impl TryFrom<hyper::Response> for DesiredContent
(This assumes you might have cases that should fail like - 404 errors)
I've got the following method:
fn get_error_id(err: CustomError) -> i64 {
let default_id = 0;
match err {
CustomError::Unknown(response) => {
if response.status == StatusCode::NOT_FOUND {
404
} else {
default_id
}
}
_ => default_id,
}
}
Is there any way to refactor it to inline default_id?
Match arms can include if expressions, so you can simplify the code like so:
fn get_error_id(err: CustomError) -> i64 {
match err {
CustomError::Unknown(ref r) if r.status == StatusCode::NOT_FOUND => 404,
_ => 0, // default_id
}
}