Add special handling of FromRequest extractors not being the last arg (#1797)

This commit is contained in:
David Pedersen
2023-03-03 09:44:10 +01:00
committed by GitHub
parent 73be489e03
commit 416a0568d3
8 changed files with 157 additions and 33 deletions
+111 -5
View File
@@ -47,13 +47,22 @@ pub(crate) fn expand(attr: Attrs, item_fn: ItemFn) -> TokenStream {
err.unwrap_or_else(|| {
let state_ty = state_ty.unwrap_or_else(|| syn::parse_quote!(()));
let check_inputs_impls_from_request =
check_inputs_impls_from_request(&item_fn, &body_ty, state_ty);
let check_input_order = check_input_order(&item_fn);
let check_future_send = check_future_send(&item_fn);
quote! {
#check_inputs_impls_from_request
#check_future_send
if let Some(check_input_order) = check_input_order {
quote! {
#check_input_order
#check_future_send
}
} else {
let check_inputs_impls_from_request =
check_inputs_impls_from_request(&item_fn, &body_ty, state_ty);
quote! {
#check_inputs_impls_from_request
#check_future_send
}
}
})
} else {
@@ -278,6 +287,103 @@ fn check_inputs_impls_from_request(
.collect::<TokenStream>()
}
fn check_input_order(item_fn: &ItemFn) -> Option<TokenStream> {
let types_that_consume_the_request = item_fn
.sig
.inputs
.iter()
.enumerate()
.filter_map(|(idx, arg)| {
let ty = match arg {
FnArg::Typed(pat_type) => &*pat_type.ty,
FnArg::Receiver(_) => return None,
};
let span = ty.span();
let path = match ty {
Type::Path(type_path) => &type_path.path,
_ => return None,
};
let ident = match path.segments.last() {
Some(path_segment) => &path_segment.ident,
None => return None,
};
let type_name = match &*ident.to_string() {
"Json" => "Json<_>",
"BodyStream" => "BodyStream",
"RawBody" => "RawBody<_>",
"RawForm" => "RawForm",
"Multipart" => "Multipart",
"Protobuf" => "Protobuf",
"JsonLines" => "JsonLines<_>",
"Form" => "Form<_>",
"Request" => "Request<_>",
"Bytes" => "Bytes",
"String" => "String",
"Parts" => "Parts",
_ => return None,
};
Some((idx, type_name, span))
})
.collect::<Vec<_>>();
if types_that_consume_the_request.is_empty() {
return None;
};
// exactly one type that consumes the request
if types_that_consume_the_request.len() == 1 {
// and that is not the last
if types_that_consume_the_request[0].0 != item_fn.sig.inputs.len() - 1 {
let (_idx, type_name, span) = &types_that_consume_the_request[0];
let error = format!(
"`{type_name}` consumes the request body and thus must be \
the last argument to the handler function"
);
return Some(quote_spanned! {*span=>
compile_error!(#error);
});
} else {
return None;
}
}
if types_that_consume_the_request.len() == 2 {
let (_, first, _) = &types_that_consume_the_request[0];
let (_, second, _) = &types_that_consume_the_request[1];
let error = format!(
"Can't have two extractors that consume the request body. \
`{first}` and `{second}` both do that.",
);
let span = item_fn.sig.inputs.span();
Some(quote_spanned! {span=>
compile_error!(#error);
})
} else {
let types = WithPosition::new(types_that_consume_the_request.into_iter())
.map(|pos| match pos {
Position::First((_, type_name, _)) | Position::Middle((_, type_name, _)) => {
format!("`{type_name}`, ")
}
Position::Last((_, type_name, _)) => format!("and `{type_name}`"),
Position::Only(_) => unreachable!(),
})
.collect::<String>();
let error = format!(
"Can't have more than one extractor that consume the request body. \
{types} all do that.",
);
let span = item_fn.sig.inputs.span();
Some(quote_spanned! {span=>
compile_error!(#error);
})
}
}
fn check_output_impls_into_response(item_fn: &ItemFn) -> TokenStream {
let ty = match &item_fn.sig.output {
syn::ReturnType::Default => return quote! {},