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
}
}
Related
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>()?)
}
Minimal example of the structure of my code (playground link):
struct Error;
fn answer() -> Result<Option<i64>, Error> {
(0..100_i64)
.map(|i| -> Result<Option<i64>, Error> {
let candidate = i * 7;
if candidate <= 42 {
Ok(Some(candidate))
} else if candidate == 666 {
Err(Error)
} else {
Ok(None)
}
})
.max()
}
The goal is to take the maximum over the i64 values, returning Ok(None) if none of the Options contained a value, and immediately returning Err(Error) if any of the values were Err(Error).
Of course this doesn't compile as is, because we can't take the max() over an iterable of Results.
With a plain for loop, this would be possible (but inelegant):
fn answer() -> Result<Option<i64>, Error> {
let items = (0..100_i64)
.map(|i| -> Result<Option<i64>, Error> {
let candidate = i * 7;
if candidate <= 42 {
Ok(Some(candidate))
} else if candidate == 666 {
Err(Error)
} else {
Ok(None)
}
});
let mut max = None;
for item in items {
match item {
Ok(candidate) => {
// Conveniently, None < Some(_).
max = std::cmp::max(max, candidate);
}
Err(Error) => {
return Err(Error);
}
}
}
Ok(max)
}
Can it be done using chaining syntax and ? instead?
If you don't want to use an external crate, you can use Iterator's try_fold adaptor, which is only a little more verbose:
struct Error;
fn answer() -> Result<Option<i64>, Error> {
(0..100_i64)
.map(|i| -> Result<Option<i64>, Error> {
let candidate = i * 7;
if candidate <= 42 {
Ok(Some(candidate))
} else if candidate == 666 {
Err(Error)
} else {
Ok(None)
}
})
.try_fold(None, |prev, next| next.map(|ok| std::cmp::max(prev, ok)))
}
Using Itertools::fold_ok from the itertools crate:
fn answer() -> Result<Option<i64>, Error> {
(0..100_i64)
.map(|i| -> Result<Option<i64>, Error> {
let candidate = i * 7;
if candidate <= 42 {
Ok(Some(candidate))
} else if candidate == 666 {
Err(Error)
} else {
Ok(None)
}
})
.fold_ok(None, std::cmp::max) // Conveniently, None < Some(_)
}
I guess that the very existence of this function means that we'd need a Result-aware max function, like max_ok, in order to do this more cleanly.
I have this code in Go:
_, err := kms.ScheduleKeyDeletion(...
ev, ok := err.(awserr.Error)
deleted := false
if ok {
switch ev.Code() {
case "KMSInvalidStateException":
deleted = strings.Contains(aerr.Message(), "pending deletion")
case "NotFoundException":
deleted = true
}
}
Basically, want to do the same in Rust using https://docs.rs/aws-sdk-kms/latest/aws_sdk_kms/client/struct.Client.html#method.schedule_key_deletion
let result = cli.schedule_key_deletion().send().await;
let success = match result {
Ok(ok_result) => { ... },
Err(e) => {
// "e" is the 'aws_sdk_kms::error::ScheduleKeyDeletionError' type
// ref. https://docs.rs/aws-sdk-kms/latest/aws_sdk_kms/error/struct.ScheduleKeyDeletionError.html
match e {
// doesn't compile...
aws_sdk_kms::error::NotFoundException => {
true
},
_ => false
}
}
}
Basically, on the return type https://docs.rs/aws-sdk-kms/latest/aws_sdk_kms/error/struct.ScheduleKeyDeletionError.html, I want to check if the return struct is https://docs.rs/aws-sdk-kms/latest/aws_sdk_kms/error/struct.NotFoundException.html or not.
#[non_exhaustive]
pub struct ScheduleKeyDeletionError {
pub kind: ScheduleKeyDeletionErrorKind,
/* private fields */
}
#[non_exhaustive]
pub struct NotFoundException {
pub message: Option<String>,
}
You can use e.kind() to get the Kind of error that occurred. The return value of e.kind will be of Enum type ScheduleKeyDeletionErrorKind. So you can match the enum variant
aws_sdk_kms::error::ScheduleKeyDeletionErrorKind::NotFoundException to see if there an NotFoundException Exception.
let result = cli.schedule_key_deletion().send().await;
let success = match result {
Ok(ok_result) => { ... },
Err(e) => {
match e.kind() {
aws_sdk_kms::error::ScheduleKeyDeletionErrorKind::NotFoundException(_)=> {
true
},
_ => false
}
}
}
Note: This solution is not tested.
I'm using syn to parse Rust code. When I read a named field's type using field.ty, I get a syn::Type. When I print it using quote!{#ty}.to_string() I get "Option<String>".
How can I get just "String"? I want to use #ty in quote! to print "String" instead of "Option<String>".
I want to generate code like:
impl Foo {
pub set_bar(&mut self, v: String) {
self.bar = Some(v);
}
}
starting from
struct Foo {
bar: Option<String>
}
My attempt:
let ast: DeriveInput = parse_macro_input!(input as DeriveInput);
let data: Data = ast.data;
match data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => {
fields.named.iter().for_each(|field| {
let name = &field.ident.clone().unwrap();
let ty = &field.ty;
quote!{
impl Foo {
pub set_bar(&mut self, v: #ty) {
self.bar = Some(v);
}
}
};
});
}
_ => {}
},
_ => panic!("You can derive it only from struct"),
}
My updated version of the response from #Boiethios, tested and used in a public crate, with support of several syntaxes for Option:
Option
std::option::Option
::std::option::Option
core::option::Option
::core::option::Option
fn extract_type_from_option(ty: &syn::Type) -> Option<&syn::Type> {
use syn::{GenericArgument, Path, PathArguments, PathSegment};
fn extract_type_path(ty: &syn::Type) -> Option<&Path> {
match *ty {
syn::Type::Path(ref typepath) if typepath.qself.is_none() => Some(&typepath.path),
_ => None,
}
}
// TODO store (with lazy static) the vec of string
// TODO maybe optimization, reverse the order of segments
fn extract_option_segment(path: &Path) -> Option<&PathSegment> {
let idents_of_path = path
.segments
.iter()
.into_iter()
.fold(String::new(), |mut acc, v| {
acc.push_str(&v.ident.to_string());
acc.push('|');
acc
});
vec!["Option|", "std|option|Option|", "core|option|Option|"]
.into_iter()
.find(|s| &idents_of_path == *s)
.and_then(|_| path.segments.last())
}
extract_type_path(ty)
.and_then(|path| extract_option_segment(path))
.and_then(|path_seg| {
let type_params = &path_seg.arguments;
// It should have only on angle-bracketed param ("<String>"):
match *type_params {
PathArguments::AngleBracketed(ref params) => params.args.first(),
_ => None,
}
})
.and_then(|generic_arg| match *generic_arg {
GenericArgument::Type(ref ty) => Some(ty),
_ => None,
})
}
You should do something like this untested example:
use syn::{GenericArgument, PathArguments, Type};
fn extract_type_from_option(ty: &Type) -> Type {
fn path_is_option(path: &Path) -> bool {
leading_colon.is_none()
&& path.segments.len() == 1
&& path.segments.iter().next().unwrap().ident == "Option"
}
match ty {
Type::Path(typepath) if typepath.qself.is_none() && path_is_option(typepath.path) => {
// Get the first segment of the path (there is only one, in fact: "Option"):
let type_params = typepath.path.segments.iter().first().unwrap().arguments;
// It should have only on angle-bracketed param ("<String>"):
let generic_arg = match type_params {
PathArguments::AngleBracketed(params) => params.args.iter().first().unwrap(),
_ => panic!("TODO: error handling"),
};
// This argument must be a type:
match generic_arg {
GenericArgument::Type(ty) => ty,
_ => panic!("TODO: error handling"),
}
}
_ => panic!("TODO: error handling"),
}
}
There's not many things to explain, it just "unrolls" the diverse components of a type:
Type -> TypePath -> Path -> PathSegment -> PathArguments -> AngleBracketedGenericArguments -> GenericArgument -> Type.
If there is an easier way to do that, I would be happy to know it.
Note that since syn is a parser, it works with tokens. You cannot know for sure that this is an Option. The user could, for example, type std::option::Option, or write type MaybeString = std::option::Option<String>;. You cannot handle those arbitrary names.
In my code below I find that the code in match_num_works() has a certain elegance. I would like to write a String match with a similar formulation but cannot get it to work. I end up with match_text_works() which is less elegant.
struct FooNum {
number: i32,
}
// Elegant
fn match_num_works(foo_num: &FooNum) {
match foo_num {
&FooNum { number: 1 } => (),
_ => (),
}
}
struct FooText {
text: String,
}
// Clunky
fn match_text_works(foo_text: &FooText) {
match foo_text {
&FooText { ref text } => {
if text == "pattern" {
} else {
}
}
}
}
// Possible?
fn match_text_fails(foo_text: &FooText) {
match foo_text {
&FooText { text: "pattern" } => (),
_ => (),
}
}
Its probably not "elegant" or any nicer.. but one option is to move the conditional into the match expression:
match foo_text {
&FooText { ref text } if text == "pattern" => (),
_ => ()
}
Working sample: Playpen link.
Note that your desired pattern would actually work with a &str. You can't directly pattern match a String because it's a more complex value that includes an unexposed internal buffer.
struct FooText<'a> {
text: &'a str,
_other: u32,
}
fn main() {
let foo = FooText { text: "foo", _other: 5 };
match foo {
FooText { text: "foo", .. } => println!("Match!"),
_ => println!("No match"),
}
}
Playground