Skip to content

Commit c0ad038

Browse files
committed
Working example
1 parent 56e7fa6 commit c0ad038

File tree

16 files changed

+159
-90
lines changed

16 files changed

+159
-90
lines changed

core/codegen/src/attribute/catch/mod.rs

+15-10
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub fn _catch(
2323
let deprecated = catch.function.attrs.iter().find(|a| a.path().is_ident("deprecated"));
2424

2525
// Determine the number of parameters that will be passed in.
26-
if catch.function.sig.inputs.len() > 2 {
26+
if catch.function.sig.inputs.len() > 3 {
2727
return Err(catch.function.sig.paren_token.span.join()
2828
.error("invalid number of arguments: must be zero, one, or two")
2929
.help("catchers optionally take `&Request` or `Status, &Request`"));
@@ -36,7 +36,6 @@ pub fn _catch(
3636

3737
// TODO: how to handle request?
3838
// - Right now: (), (&Req), (Status, &Req) allowed
39-
// - New: (), (&E), (&Req, &E), (Status, &Req, &E)
4039
// Set the `req` and `status` spans to that of their respective function
4140
// arguments for a more correct `wrong type` error span. `rev` to be cute.
4241
let codegen_args = &[__req, __status, __error];
@@ -46,9 +45,13 @@ pub fn _catch(
4645
syn::FnArg::Receiver(_) => codegen_arg.respanned(fn_arg.span()),
4746
syn::FnArg::Typed(a) => codegen_arg.respanned(a.ty.span())
4847
}).rev();
49-
let make_error = if let Some(arg) = catch.function.sig.inputs.iter().rev().next() {
48+
let make_error = if catch.function.sig.inputs.len() >= 3 {
49+
let arg = catch.function.sig.inputs.first().unwrap();
5050
quote_spanned!(arg.span() =>
51-
// let
51+
let #__error = match ::rocket::catcher::downcast(__error_init.as_ref()) {
52+
Some(v) => v,
53+
None => return #_Result::Err((#__status, __error_init)),
54+
};
5255
)
5356
} else {
5457
quote! {}
@@ -60,7 +63,7 @@ pub fn _catch(
6063

6164
let catcher_response = quote_spanned!(return_type_span => {
6265
let ___responder = #user_catcher_fn_name(#(#inputs),*) #dot_await;
63-
#_response::Responder::respond_to(___responder, #__req)?
66+
#_response::Responder::respond_to(___responder, #__req).map_err(|s| (s, __error_init))?
6467
});
6568

6669
// Generate the catcher, keeping the user's input around.
@@ -79,15 +82,17 @@ pub fn _catch(
7982
fn monomorphized_function<'__r>(
8083
#__status: #Status,
8184
#__req: &'__r #Request<'_>,
82-
__error_init: &#ErasedErrorRef<'__r>,
85+
__error_init: #ErasedError<'__r>,
8386
) -> #_catcher::BoxFuture<'__r> {
8487
#_Box::pin(async move {
8588
#make_error
8689
let __response = #catcher_response;
87-
#Response::build()
88-
.status(#__status)
89-
.merge(__response)
90-
.ok()
90+
#_Result::Ok(
91+
#Response::build()
92+
.status(#__status)
93+
.merge(__response)
94+
.finalize()
95+
)
9196
})
9297
}
9398

core/codegen/src/attribute/catch/parse.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use devise::ext::SpanDiagnosticExt;
2-
use devise::{MetaItem, Spanned, Result, FromMeta, Diagnostic};
2+
use devise::{Diagnostic, FromMeta, MetaItem, Result, SpanWrapped, Spanned};
33
use proc_macro2::TokenStream;
44

5+
use crate::attribute::param::Dynamic;
56
use crate::{http, http_codegen};
67

78
/// This structure represents the parsed `catch` attribute and associated items.
@@ -10,13 +11,15 @@ pub struct Attribute {
1011
pub status: Option<http::Status>,
1112
/// The function that was decorated with the `catch` attribute.
1213
pub function: syn::ItemFn,
14+
pub error: Option<SpanWrapped<Dynamic>>,
1315
}
1416

1517
/// We generate a full parser for the meta-item for great error messages.
1618
#[derive(FromMeta)]
1719
struct Meta {
1820
#[meta(naked)]
1921
code: Code,
22+
// error: Option<SpanWrapped<Dynamic>>,
2023
}
2124

2225
/// `Some` if there's a code, `None` if it's `default`.
@@ -49,10 +52,10 @@ impl Attribute {
4952

5053
let attr: MetaItem = syn::parse2(quote!(catch(#args)))?;
5154
let status = Meta::from_meta(&attr)
52-
.map(|meta| meta.code.0)
55+
.map(|meta| meta)
5356
.map_err(|diag| diag.help("`#[catch]` expects a status code int or `default`: \
5457
`#[catch(404)]` or `#[catch(default)]`"))?;
5558

56-
Ok(Attribute { status, function })
59+
Ok(Attribute { status: status.code.0, function, error: status.error })
5760
}
5861
}

core/codegen/src/attribute/route/mod.rs

+14-17
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
4141
}
4242

4343
define_spanned_export!(Span::call_site() =>
44-
__req, __data, _form, Outcome, _Ok, _Err, _Some, _None, Status
44+
__req, __data, _form, Outcome, _Ok, _Err, _Some, _None, Status, resolve_error
4545
);
4646

4747
// Record all of the static parameters for later filtering.
@@ -108,13 +108,13 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
108108
::rocket::trace::span_info!(
109109
"codegen",
110110
"query string failed to match route declaration" =>
111-
{ for _err in __e { ::rocket::trace::info!(
111+
{ for _err in __e.iter() { ::rocket::trace::info!(
112112
target: concat!("rocket::codegen::route::", module_path!()),
113113
"{_err}"
114114
); } }
115115
);
116116

117-
return #Outcome::Forward((#__data, #Status::UnprocessableEntity));
117+
return #Outcome::Forward((#__data, #Status::UnprocessableEntity, #resolve_error!(__e)));
118118
}
119119

120120
(#(#ident.unwrap()),*)
@@ -125,7 +125,7 @@ fn query_decls(route: &Route) -> Option<TokenStream> {
125125
fn request_guard_decl(guard: &Guard) -> TokenStream {
126126
let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty);
127127
define_spanned_export!(ty.span() =>
128-
__req, __data, _request, display_hack, FromRequest, Outcome, ErrorResolver, ErrorDefault
128+
__req, __data, _request, display_hack, FromRequest, Outcome, resolve_error
129129
);
130130

131131
quote_spanned! { ty.span() =>
@@ -141,7 +141,7 @@ fn request_guard_decl(guard: &Guard) -> TokenStream {
141141
"request guard forwarding"
142142
);
143143

144-
return #Outcome::Forward((#__data, __e));
144+
return #Outcome::Forward((#__data, __e, #resolve_error!()));
145145
},
146146
#[allow(unreachable_code)]
147147
#Outcome::Error((__c, __e)) => {
@@ -154,9 +154,7 @@ fn request_guard_decl(guard: &Guard) -> TokenStream {
154154
"request guard failed"
155155
);
156156

157-
#[allow(unused)]
158-
use #ErrorDefault;
159-
return #Outcome::Error((__c, #ErrorResolver::new(__e).cast()));
157+
return #Outcome::Error((__c, #resolve_error!(__e)));
160158
}
161159
};
162160
}
@@ -166,7 +164,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
166164
let (i, name, ty) = (guard.index, &guard.name, &guard.ty);
167165
define_spanned_export!(ty.span() =>
168166
__req, __data, _None, _Some, _Ok, _Err,
169-
Outcome, FromSegments, FromParam, Status, display_hack
167+
Outcome, FromSegments, FromParam, Status, display_hack, resolve_error
170168
);
171169

172170
// Returned when a dynamic parameter fails to parse.
@@ -176,11 +174,12 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
176174
target: concat!("rocket::codegen::route::", module_path!()),
177175
parameter = #name,
178176
type_name = stringify!(#ty),
179-
reason = %#display_hack!(__error),
177+
error_ty = std::any::type_name_of_val(&__error),
178+
reason = %#display_hack!(&__error),
180179
"path guard forwarding"
181180
);
182181

183-
#Outcome::Forward((#__data, #Status::UnprocessableEntity))
182+
#Outcome::Forward((#__data, #Status::UnprocessableEntity, #resolve_error!(__error)))
184183
});
185184

186185
// All dynamic parameters should be found if this function is being called;
@@ -202,7 +201,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
202201
#i
203202
);
204203

205-
return #Outcome::Forward((#__data, #Status::InternalServerError));
204+
return #Outcome::Forward((#__data, #Status::InternalServerError, #resolve_error!()));
206205
}
207206
}
208207
},
@@ -221,7 +220,7 @@ fn param_guard_decl(guard: &Guard) -> TokenStream {
221220

222221
fn data_guard_decl(guard: &Guard) -> TokenStream {
223222
let (ident, ty) = (guard.fn_ident.rocketized(), &guard.ty);
224-
define_spanned_export!(ty.span() => __req, __data, display_hack, FromData, Outcome, ErrorResolver, ErrorDefault);
223+
define_spanned_export!(ty.span() => __req, __data, display_hack, FromData, Outcome, resolve_error);
225224

226225
quote_spanned! { ty.span() =>
227226
let #ident: #ty = match <#ty as #FromData>::from_data(#__req, #__data).await {
@@ -236,7 +235,7 @@ fn data_guard_decl(guard: &Guard) -> TokenStream {
236235
"data guard forwarding"
237236
);
238237

239-
return #Outcome::Forward((__d, __e));
238+
return #Outcome::Forward((__d, __e, #resolve_error!()));
240239
}
241240
#[allow(unreachable_code)]
242241
#Outcome::Error((__c, __e)) => {
@@ -249,9 +248,7 @@ fn data_guard_decl(guard: &Guard) -> TokenStream {
249248
"data guard failed"
250249
);
251250

252-
#[allow(unused)]
253-
use #ErrorDefault;
254-
return #Outcome::Error((__c, #ErrorResolver::new(__e).cast()));
251+
return #Outcome::Error((__c, #resolve_error!(__e)));
255252
}
256253
};
257254
}

core/codegen/src/exports.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,8 @@ define_exported_paths! {
102102
Route => ::rocket::Route,
103103
Catcher => ::rocket::Catcher,
104104
Status => ::rocket::http::Status,
105-
ErrorResolver => ::rocket::catcher::resolution::Resolve,
106-
ErrorDefault => ::rocket::catcher::resolution::DefaultTypeErase,
107-
ErasedErrorRef => ::rocket::catcher::ErasedErrorRef,
105+
resolve_error => ::rocket::catcher::resolve_typed_catcher,
106+
ErasedError => ::rocket::catcher::ErasedError,
108107
}
109108

110109
macro_rules! define_spanned_export {

core/http/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ memchr = "2"
3636
stable-pattern = "0.1"
3737
cookie = { version = "0.18", features = ["percent-encode"] }
3838
state = "0.6"
39+
transient = { version = "0.2.1" }
3940

4041
[dependencies.serde]
4142
version = "1.0"

core/http/src/uri/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Errors arising from parsing invalid URIs.
22
33
use std::fmt;
4+
use transient::Static;
45

56
pub use crate::parse::uri::Error;
67

@@ -29,6 +30,8 @@ pub enum PathError {
2930
BadEnd(char),
3031
}
3132

33+
impl Static for PathError {}
34+
3235
impl fmt::Display for PathError {
3336
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3437
match self {

core/lib/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ tokio-stream = { version = "0.1.6", features = ["signal", "time"] }
7474
cookie = { version = "0.18", features = ["percent-encode"] }
7575
futures = { version = "0.3.30", default-features = false, features = ["std"] }
7676
state = "0.6"
77-
transient = { version = "0.2.0", path = "../../../transient" }
77+
transient = { version = "0.2.1" }
7878

7979
# tracing
8080
tracing = { version = "0.1.40", default-features = false, features = ["std", "attributes"] }

core/lib/src/catcher/catcher.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::request::Request;
88
use crate::http::{Status, ContentType, uri};
99
use crate::catcher::{Handler, BoxFuture};
1010

11-
use super::ErasedErrorRef;
11+
use super::ErasedError;
1212

1313
/// An error catching route.
1414
///
@@ -315,7 +315,7 @@ impl Catcher {
315315

316316
impl Default for Catcher {
317317
fn default() -> Self {
318-
fn handler<'r>(s: Status, req: &'r Request<'_>, _e: &ErasedErrorRef<'r>) -> BoxFuture<'r> {
318+
fn handler<'r>(s: Status, req: &'r Request<'_>, _e: ErasedError<'r>) -> BoxFuture<'r> {
319319
Box::pin(async move { Ok(default_handler(s, req)) })
320320
}
321321

@@ -333,7 +333,7 @@ pub struct StaticInfo {
333333
/// The catcher's status code.
334334
pub code: Option<u16>,
335335
/// The catcher's handler, i.e, the annotated function.
336-
pub handler: for<'r> fn(Status, &'r Request<'_>, &ErasedErrorRef<'r>) -> BoxFuture<'r>,
336+
pub handler: for<'r> fn(Status, &'r Request<'_>, ErasedError<'r>) -> BoxFuture<'r>,
337337
/// The file, line, and column where the catcher was defined.
338338
pub location: (&'static str, u32, u32),
339339
}

core/lib/src/catcher/handler.rs

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use crate::{Request, Response};
22
use crate::http::Status;
33

4-
use super::ErasedErrorRef;
4+
use super::ErasedError;
55

66
/// Type alias for the return type of a [`Catcher`](crate::Catcher)'s
77
/// [`Handler::handle()`].
8-
pub type Result<'r> = std::result::Result<Response<'r>, crate::http::Status>;
8+
pub type Result<'r> = std::result::Result<Response<'r>, (crate::http::Status, ErasedError<'r>)>;
99

1010
/// Type alias for the return type of a _raw_ [`Catcher`](crate::Catcher)'s
1111
/// [`Handler`].
@@ -99,23 +99,22 @@ pub trait Handler: Cloneable + Send + Sync + 'static {
9999
/// Nevertheless, failure is allowed, both for convenience and necessity. If
100100
/// an error handler fails, Rocket's default `500` catcher is invoked. If it
101101
/// succeeds, the returned `Response` is used to respond to the client.
102-
async fn handle<'r>(&self, status: Status, req: &'r Request<'_>, error: &ErasedErrorRef<'r>) -> Result<'r>;
102+
async fn handle<'r>(&self, status: Status, req: &'r Request<'_>, error: ErasedError<'r>) -> Result<'r>;
103103
}
104104

105105
// We write this manually to avoid double-boxing.
106106
impl<F: Clone + Sync + Send + 'static> Handler for F
107-
where for<'x> F: Fn(Status, &'x Request<'_>, &ErasedErrorRef<'x>) -> BoxFuture<'x>,
107+
where for<'x> F: Fn(Status, &'x Request<'_>, ErasedError<'x>) -> BoxFuture<'x>,
108108
{
109-
fn handle<'r, 'life0, 'life1, 'life2, 'async_trait>(
109+
fn handle<'r, 'life0, 'life1, 'async_trait>(
110110
&'life0 self,
111111
status: Status,
112112
req: &'r Request<'life1>,
113-
error: &'life2 ErasedErrorRef<'r>,
113+
error: ErasedError<'r>,
114114
) -> BoxFuture<'r>
115115
where 'r: 'async_trait,
116116
'life0: 'async_trait,
117117
'life1: 'async_trait,
118-
'life2: 'async_trait,
119118
Self: 'async_trait,
120119
{
121120
self(status, req, error)
@@ -124,7 +123,7 @@ impl<F: Clone + Sync + Send + 'static> Handler for F
124123

125124
// Used in tests! Do not use, please.
126125
#[doc(hidden)]
127-
pub fn dummy_handler<'r>(_: Status, _: &'r Request<'_>, _: &ErasedErrorRef<'r>) -> BoxFuture<'r> {
126+
pub fn dummy_handler<'r>(_: Status, _: &'r Request<'_>, _: ErasedError<'r>) -> BoxFuture<'r> {
128127
Box::pin(async move { Ok(Response::new()) })
129128
}
130129

core/lib/src/catcher/types.rs

+20-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use transient::{Any, CanRecoverFrom, Co, Transient, Downcast};
1+
use transient::{Any, CanRecoverFrom, Co, Downcast};
2+
#[doc(inline)]
3+
pub use transient::{Static, Transient};
24

35
pub type ErasedError<'r> = Box<dyn Any<Co<'r>> + Send + Sync + 'r>;
46
pub type ErasedErrorRef<'r> = dyn Any<Co<'r>> + Send + Sync + 'r;
@@ -13,19 +15,23 @@ pub fn downcast<'a, 'r, T: Transient + 'r>(v: &'a ErasedErrorRef<'r>) -> Option<
1315
v.downcast_ref()
1416
}
1517

16-
// /// Chosen not to expose this macro, since it's pretty short and sweet
17-
// #[doc(hidden)]
18-
// #[macro_export]
19-
// macro_rules! resolve_typed_catcher {
20-
// ($T:expr) => ({
21-
// #[allow(unused_imports)]
22-
// use $crate::catcher::types::Resolve;
23-
//
24-
// Resolve::new($T).cast()
25-
// })
26-
// }
27-
28-
// pub use resolve_typed_catcher;
18+
/// Upcasts a value to `ErasedError`, falling back to a default if it doesn't implement
19+
/// `Transient`
20+
#[doc(hidden)]
21+
#[macro_export]
22+
macro_rules! resolve_typed_catcher {
23+
($T:expr) => ({
24+
#[allow(unused_imports)]
25+
use $crate::catcher::resolution::{Resolve, DefaultTypeErase};
26+
27+
Resolve::new($T).cast()
28+
});
29+
() => ({
30+
$crate::catcher::default_error_type()
31+
});
32+
}
33+
34+
pub use resolve_typed_catcher;
2935

3036
pub mod resolution {
3137
use std::marker::PhantomData;

0 commit comments

Comments
 (0)