Skip to content

Commit 9841fa4

Browse files
Implement a custom error handler for unhandled or generic endpoint errors
1 parent 5ecde55 commit 9841fa4

File tree

58 files changed

+1303
-922
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1303
-922
lines changed

modules/openapi-generator/src/main/resources/rust-axum/README.mustache

+4-2
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,18 @@ struct ServerImpl {
4848

4949
#[allow(unused_variables)]
5050
#[async_trait]
51-
impl {{{packageName}}}::Api for ServerImpl {
51+
impl {{{externCrateName}}}::apis::default::Api for ServerImpl {
5252
// API implementation goes here
5353
}
5454

55+
impl {{{externCrateName}}}::apis::ErrorHandler for ServerImpl {}
56+
5557
pub async fn start_server(addr: &str) {
5658
// initialize tracing
5759
tracing_subscriber::fmt::init();
5860
5961
// Init Axum router
60-
let app = {{{packageName}}}::server::new(Arc::new(ServerImpl));
62+
let app = {{{externCrateName}}}::server::new(Arc::new(ServerImpl));
6163

6264
// Add layers to the router
6365
let app = app.layer(...);

modules/openapi-generator/src/main/resources/rust-axum/apis-mod.mustache

+14-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,17 @@ pub trait ApiKeyAuthHeader {
2727
}
2828
{{/isKeyInHeader}}
2929
{{/isApiKey}}
30-
{{/authMethods}}
30+
{{/authMethods}}
31+
32+
// Error handler for unhandled errors.
33+
#[async_trait::async_trait]
34+
pub trait ErrorHandler<E: std::fmt::Debug + Send + Sync + 'static = ()> {
35+
#[tracing::instrument(skip(self))]
36+
async fn handle_error(&self, error: E) -> Result<axum::response::Response, http::StatusCode> {
37+
tracing::error!("Unhandled error: {:?}", error);
38+
axum::response::Response::builder()
39+
.status(500)
40+
.body(axum::body::Body::empty())
41+
.map_err(|_| http::StatusCode::INTERNAL_SERVER_ERROR)
42+
}
43+
}

modules/openapi-generator/src/main/resources/rust-axum/apis.mustache

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ use crate::{models, types::*};
1717
/// {{classnamePascalCase}}
1818
#[async_trait]
1919
#[allow(clippy::ptr_arg)]
20-
pub trait {{classnamePascalCase}} {
20+
pub trait {{classnamePascalCase}}<E: std::fmt::Debug + Send + Sync + 'static = ()>: super::ErrorHandler<E> {
2121
{{#havingAuthMethod}}
22-
type Claims;
22+
type Claims;
2323
2424
{{/havingAuthMethod}}
2525
{{#operation}}
@@ -73,7 +73,7 @@ pub trait {{classnamePascalCase}} {
7373
{{#x-consumes-multipart-related}}
7474
body: axum::body::Body,
7575
{{/x-consumes-multipart-related}}
76-
) -> Result<{{{operationId}}}Response, ()>;
76+
) -> Result<{{{operationId}}}Response, E>;
7777
{{/vendorExtensions}}
7878
{{^-last}}
7979

modules/openapi-generator/src/main/resources/rust-axum/server-operation.mustache

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// {{{operationId}}} - {{{httpMethod}}} {{{basePathWithoutHost}}}{{{path}}}
22
#[tracing::instrument(skip_all)]
3-
async fn {{#vendorExtensions}}{{{x-operation-id}}}{{/vendorExtensions}}<I, A{{#havingAuthMethod}}, C{{/havingAuthMethod}}>(
3+
async fn {{#vendorExtensions}}{{{x-operation-id}}}{{/vendorExtensions}}<I, A, E{{#havingAuthMethod}}, C{{/havingAuthMethod}}>(
44
method: Method,
55
host: Host,
66
cookies: CookieJar,
@@ -54,8 +54,9 @@ async fn {{#vendorExtensions}}{{{x-operation-id}}}{{/vendorExtensions}}<I, A{{#h
5454
) -> Result<Response, StatusCode>
5555
where
5656
I: AsRef<A> + Send + Sync,
57-
A: apis::{{classFilename}}::{{classnamePascalCase}}{{#havingAuthMethod}}<Claims = C>{{/havingAuthMethod}}{{#vendorExtensions}}{{#x-has-cookie-auth-methods}}+ apis::CookieAuthentication<Claims = C>{{/x-has-cookie-auth-methods}}{{#x-has-header-auth-methods}}+ apis::ApiKeyAuthHeader<Claims = C>{{/x-has-header-auth-methods}}{{/vendorExtensions}},
58-
{
57+
A: apis::{{classFilename}}::{{classnamePascalCase}}<E{{#havingAuthMethod}}, Claims = C{{/havingAuthMethod}}>{{#vendorExtensions}}{{#x-has-cookie-auth-methods}}+ apis::CookieAuthentication<Claims = C>{{/x-has-cookie-auth-methods}}{{#x-has-header-auth-methods}}+ apis::ApiKeyAuthHeader<Claims = C>{{/x-has-header-auth-methods}}{{/vendorExtensions}} + Send + Sync,
58+
E: std::fmt::Debug + Send + Sync + 'static,
59+
{
5960
{{#vendorExtensions}}
6061
{{#x-has-auth-methods}}
6162
// Authentication
@@ -342,10 +343,10 @@ where
342343
},
343344
{{/responses}}
344345
},
345-
Err(_) => {
346+
Err(why) => {
346347
// Application code returned an error. This should not happen, as the implementation should
347348
// return a valid response.
348-
response.status(500).body(Body::empty())
349+
return api_impl.as_ref().handle_error(why).await;
349350
},
350351
};
351352

modules/openapi-generator/src/main/resources/rust-axum/server-route.mustache

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
/// Setup API Server.
2-
pub fn new<I, A{{#havingAuthMethods}}, C{{/havingAuthMethods}}>(api_impl: I) -> Router
2+
pub fn new<I, A, E{{#havingAuthMethods}}, C{{/havingAuthMethods}}>(api_impl: I) -> Router
33
where
44
I: AsRef<A> + Clone + Send + Sync + 'static,
5-
A: {{#apiInfo}}{{#apis}}{{#operations}}apis::{{classFilename}}::{{classnamePascalCase}}{{#havingAuthMethod}}<Claims = C>{{/havingAuthMethod}} + {{/operations}}{{/apis}}{{/apiInfo}}{{#authMethods}}{{#isApiKey}}{{#isKeyInCookie}}apis::CookieAuthentication<Claims = C> + {{/isKeyInCookie}}{{#isKeyInHeader}}apis::ApiKeyAuthHeader<Claims = C> + {{/isKeyInHeader}}{{/isApiKey}}{{/authMethods}}'static,
5+
A: {{#apiInfo}}{{#apis}}{{#operations}}apis::{{classFilename}}::{{classnamePascalCase}}<E{{#havingAuthMethod}}, Claims = C{{/havingAuthMethod}}> + {{/operations}}{{/apis}}{{/apiInfo}}{{#authMethods}}{{#isApiKey}}{{#isKeyInCookie}}apis::CookieAuthentication<Claims = C> + {{/isKeyInCookie}}{{#isKeyInHeader}}apis::ApiKeyAuthHeader<Claims = C> + {{/isKeyInHeader}}{{/isApiKey}}{{/authMethods}}Send + Sync + 'static,
6+
E: std::fmt::Debug + Send + Sync + 'static,
67
{{#havingAuthMethods}}C: Send + Sync + 'static,{{/havingAuthMethods}}
78
{
89
// build our application with a route
910
Router::new()
1011
{{#pathMethodOps}}
1112
.route("{{{basePathWithoutHost}}}{{{path}}}",
12-
{{#methodOperations}}{{{method}}}({{{operationID}}}::<I, A{{#vendorExtensions}}{{#havingAuthMethod}}, C{{/havingAuthMethod}}{{/vendorExtensions}}>){{^-last}}.{{/-last}}{{/methodOperations}}
13+
{{#methodOperations}}{{{method}}}({{{operationID}}}::<I, A, E{{#vendorExtensions}}{{#havingAuthMethod}}, C{{/havingAuthMethod}}{{/vendorExtensions}}>){{^-last}}.{{/-last}}{{/methodOperations}}
1314
)
1415
{{/pathMethodOps}}
1516
.with_state(api_impl)

samples/server/petstore/rust-axum/output/apikey-auths/README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,18 @@ struct ServerImpl {
4343

4444
#[allow(unused_variables)]
4545
#[async_trait]
46-
impl apikey-auths::Api for ServerImpl {
46+
impl apikey_auths::apis::default::Api for ServerImpl {
4747
// API implementation goes here
4848
}
4949

50+
impl apikey_auths::apis::ErrorHandler for ServerImpl {}
51+
5052
pub async fn start_server(addr: &str) {
5153
// initialize tracing
5254
tracing_subscriber::fmt::init();
5355

5456
// Init Axum router
55-
let app = apikey-auths::server::new(Arc::new(ServerImpl));
57+
let app = apikey_auths::server::new(Arc::new(ServerImpl));
5658

5759
// Add layers to the router
5860
let app = app.layer(...);

samples/server/petstore/rust-axum/output/apikey-auths/src/apis/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,16 @@ pub trait CookieAuthentication {
2424
key: &str,
2525
) -> Option<Self::Claims>;
2626
}
27+
28+
// Error handler for unhandled errors.
29+
#[async_trait::async_trait]
30+
pub trait ErrorHandler<E: std::fmt::Debug + Send + Sync + 'static = ()> {
31+
#[tracing::instrument(skip(self))]
32+
async fn handle_error(&self, error: E) -> Result<axum::response::Response, http::StatusCode> {
33+
tracing::error!("Unhandled error: {:?}", error);
34+
axum::response::Response::builder()
35+
.status(500)
36+
.body(axum::body::Body::empty())
37+
.map_err(|_| http::StatusCode::INTERNAL_SERVER_ERROR)
38+
}
39+
}

samples/server/petstore/rust-axum/output/apikey-auths/src/apis/payments.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ pub enum PostMakePaymentResponse {
3838
/// Payments
3939
#[async_trait]
4040
#[allow(clippy::ptr_arg)]
41-
pub trait Payments {
41+
pub trait Payments<E: std::fmt::Debug + Send + Sync + 'static = ()>:
42+
super::ErrorHandler<E>
43+
{
4244
type Claims;
4345

4446
/// Get payment method by id.
@@ -50,7 +52,7 @@ pub trait Payments {
5052
host: Host,
5153
cookies: CookieJar,
5254
path_params: models::GetPaymentMethodByIdPathParams,
53-
) -> Result<GetPaymentMethodByIdResponse, ()>;
55+
) -> Result<GetPaymentMethodByIdResponse, E>;
5456

5557
/// Get payment methods.
5658
///
@@ -60,7 +62,7 @@ pub trait Payments {
6062
method: Method,
6163
host: Host,
6264
cookies: CookieJar,
63-
) -> Result<GetPaymentMethodsResponse, ()>;
65+
) -> Result<GetPaymentMethodsResponse, E>;
6466

6567
/// Make a payment.
6668
///
@@ -72,5 +74,5 @@ pub trait Payments {
7274
cookies: CookieJar,
7375
claims: Self::Claims,
7476
body: Option<models::Payment>,
75-
) -> Result<PostMakePaymentResponse, ()>;
77+
) -> Result<PostMakePaymentResponse, E>;
7678
}

samples/server/petstore/rust-axum/output/apikey-auths/src/server/mod.rs

+29-17
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,29 @@ use crate::{header, types::*};
1313
use crate::{apis, models};
1414

1515
/// Setup API Server.
16-
pub fn new<I, A, C>(api_impl: I) -> Router
16+
pub fn new<I, A, E, C>(api_impl: I) -> Router
1717
where
1818
I: AsRef<A> + Clone + Send + Sync + 'static,
19-
A: apis::payments::Payments<Claims = C>
19+
A: apis::payments::Payments<E, Claims = C>
2020
+ apis::ApiKeyAuthHeader<Claims = C>
2121
+ apis::CookieAuthentication<Claims = C>
22+
+ Send
23+
+ Sync
2224
+ 'static,
25+
E: std::fmt::Debug + Send + Sync + 'static,
2326
C: Send + Sync + 'static,
2427
{
2528
// build our application with a route
2629
Router::new()
27-
.route("/v71/paymentMethods", get(get_payment_methods::<I, A, C>))
30+
.route(
31+
"/v71/paymentMethods",
32+
get(get_payment_methods::<I, A, E, C>),
33+
)
2834
.route(
2935
"/v71/paymentMethods/:id",
30-
get(get_payment_method_by_id::<I, A, C>),
36+
get(get_payment_method_by_id::<I, A, E, C>),
3137
)
32-
.route("/v71/payments", post(post_make_payment::<I, A, C>))
38+
.route("/v71/payments", post(post_make_payment::<I, A, E, C>))
3339
.with_state(api_impl)
3440
}
3541

@@ -43,7 +49,7 @@ fn get_payment_method_by_id_validation(
4349
}
4450
/// GetPaymentMethodById - GET /v71/paymentMethods/{id}
4551
#[tracing::instrument(skip_all)]
46-
async fn get_payment_method_by_id<I, A, C>(
52+
async fn get_payment_method_by_id<I, A, E, C>(
4753
method: Method,
4854
host: Host,
4955
cookies: CookieJar,
@@ -52,7 +58,8 @@ async fn get_payment_method_by_id<I, A, C>(
5258
) -> Result<Response, StatusCode>
5359
where
5460
I: AsRef<A> + Send + Sync,
55-
A: apis::payments::Payments<Claims = C>,
61+
A: apis::payments::Payments<E, Claims = C> + Send + Sync,
62+
E: std::fmt::Debug + Send + Sync + 'static,
5663
{
5764
#[allow(clippy::redundant_closure)]
5865
let validation =
@@ -123,10 +130,10 @@ where
123130
response.body(Body::from(body_content))
124131
}
125132
},
126-
Err(_) => {
133+
Err(why) => {
127134
// Application code returned an error. This should not happen, as the implementation should
128135
// return a valid response.
129-
response.status(500).body(Body::empty())
136+
return api_impl.as_ref().handle_error(why).await;
130137
}
131138
};
132139

@@ -142,15 +149,16 @@ fn get_payment_methods_validation() -> std::result::Result<(), ValidationErrors>
142149
}
143150
/// GetPaymentMethods - GET /v71/paymentMethods
144151
#[tracing::instrument(skip_all)]
145-
async fn get_payment_methods<I, A, C>(
152+
async fn get_payment_methods<I, A, E, C>(
146153
method: Method,
147154
host: Host,
148155
cookies: CookieJar,
149156
State(api_impl): State<I>,
150157
) -> Result<Response, StatusCode>
151158
where
152159
I: AsRef<A> + Send + Sync,
153-
A: apis::payments::Payments<Claims = C>,
160+
A: apis::payments::Payments<E, Claims = C> + Send + Sync,
161+
E: std::fmt::Debug + Send + Sync + 'static,
154162
{
155163
#[allow(clippy::redundant_closure)]
156164
let validation = tokio::task::spawn_blocking(move || get_payment_methods_validation())
@@ -197,10 +205,10 @@ where
197205
response.body(Body::from(body_content))
198206
}
199207
},
200-
Err(_) => {
208+
Err(why) => {
201209
// Application code returned an error. This should not happen, as the implementation should
202210
// return a valid response.
203-
response.status(500).body(Body::empty())
211+
return api_impl.as_ref().handle_error(why).await;
204212
}
205213
};
206214

@@ -230,7 +238,7 @@ fn post_make_payment_validation(
230238
}
231239
/// PostMakePayment - POST /v71/payments
232240
#[tracing::instrument(skip_all)]
233-
async fn post_make_payment<I, A, C>(
241+
async fn post_make_payment<I, A, E, C>(
234242
method: Method,
235243
host: Host,
236244
cookies: CookieJar,
@@ -239,7 +247,11 @@ async fn post_make_payment<I, A, C>(
239247
) -> Result<Response, StatusCode>
240248
where
241249
I: AsRef<A> + Send + Sync,
242-
A: apis::payments::Payments<Claims = C> + apis::CookieAuthentication<Claims = C>,
250+
A: apis::payments::Payments<E, Claims = C>
251+
+ apis::CookieAuthentication<Claims = C>
252+
+ Send
253+
+ Sync,
254+
E: std::fmt::Debug + Send + Sync + 'static,
243255
{
244256
// Authentication
245257
let claims_in_cookie = api_impl
@@ -322,10 +334,10 @@ where
322334
response.body(Body::from(body_content))
323335
}
324336
},
325-
Err(_) => {
337+
Err(why) => {
326338
// Application code returned an error. This should not happen, as the implementation should
327339
// return a valid response.
328-
response.status(500).body(Body::empty())
340+
return api_impl.as_ref().handle_error(why).await;
329341
}
330342
};
331343

samples/server/petstore/rust-axum/output/multipart-v3/README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,18 @@ struct ServerImpl {
4343

4444
#[allow(unused_variables)]
4545
#[async_trait]
46-
impl multipart-v3::Api for ServerImpl {
46+
impl multipart_v3::apis::default::Api for ServerImpl {
4747
// API implementation goes here
4848
}
4949

50+
impl multipart_v3::apis::ErrorHandler for ServerImpl {}
51+
5052
pub async fn start_server(addr: &str) {
5153
// initialize tracing
5254
tracing_subscriber::fmt::init();
5355

5456
// Init Axum router
55-
let app = multipart-v3::server::new(Arc::new(ServerImpl));
57+
let app = multipart_v3::server::new(Arc::new(ServerImpl));
5658

5759
// Add layers to the router
5860
let app = app.layer(...);

samples/server/petstore/rust-axum/output/multipart-v3/src/apis/default.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ pub enum MultipleIdenticalMimeTypesPostResponse {
3434
/// Default
3535
#[async_trait]
3636
#[allow(clippy::ptr_arg)]
37-
pub trait Default {
37+
pub trait Default<E: std::fmt::Debug + Send + Sync + 'static = ()>: super::ErrorHandler<E> {
3838
/// MultipartRelatedRequestPost - POST /multipart_related_request
3939
async fn multipart_related_request_post(
4040
&self,
4141
method: Method,
4242
host: Host,
4343
cookies: CookieJar,
4444
body: axum::body::Body,
45-
) -> Result<MultipartRelatedRequestPostResponse, ()>;
45+
) -> Result<MultipartRelatedRequestPostResponse, E>;
4646

4747
/// MultipartRequestPost - POST /multipart_request
4848
async fn multipart_request_post(
@@ -51,7 +51,7 @@ pub trait Default {
5151
host: Host,
5252
cookies: CookieJar,
5353
body: Multipart,
54-
) -> Result<MultipartRequestPostResponse, ()>;
54+
) -> Result<MultipartRequestPostResponse, E>;
5555

5656
/// MultipleIdenticalMimeTypesPost - POST /multiple-identical-mime-types
5757
async fn multiple_identical_mime_types_post(
@@ -60,5 +60,5 @@ pub trait Default {
6060
host: Host,
6161
cookies: CookieJar,
6262
body: axum::body::Body,
63-
) -> Result<MultipleIdenticalMimeTypesPostResponse, ()>;
63+
) -> Result<MultipleIdenticalMimeTypesPostResponse, E>;
6464
}
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
11
pub mod default;
2+
3+
// Error handler for unhandled errors.
4+
#[async_trait::async_trait]
5+
pub trait ErrorHandler<E: std::fmt::Debug + Send + Sync + 'static = ()> {
6+
#[tracing::instrument(skip(self))]
7+
async fn handle_error(&self, error: E) -> Result<axum::response::Response, http::StatusCode> {
8+
tracing::error!("Unhandled error: {:?}", error);
9+
axum::response::Response::builder()
10+
.status(500)
11+
.body(axum::body::Body::empty())
12+
.map_err(|_| http::StatusCode::INTERNAL_SERVER_ERROR)
13+
}
14+
}

0 commit comments

Comments
 (0)