Skip to content

Commit adbbe68

Browse files
[Rust-Axum][Breaking Change] Implement a customizable error handler (#20463)
* Implement a custom error handler for unhandled or generic endpoint errors * Pass in method, host and cookies to error handler * Update axum to 0.8 * Make API methods take references instead of ownership * Multipart is also part of the axum update * Prevent replacing path names with the same name as a dynamic path parameter * Use status code name instead of number * Rollback axum update * Forgot paths
1 parent ba0456a commit adbbe68

File tree

81 files changed

+2695
-1649
lines changed

Some content is hidden

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

81 files changed

+2695
-1649
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ tokio = { version = "1", default-features = false, features = [
6262
] }
6363
tracing = { version = "0.1", features = ["attributes"] }
6464
uuid = { version = "1", features = ["serde"] }
65-
validator = { version = "0.19", features = ["derive"] }
65+
validator = { version = "0.20", features = ["derive"] }
6666

6767
[dev-dependencies]
6868
tracing-subscriber = "0.3"

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

Lines changed: 4 additions & 2 deletions
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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,24 @@ 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+
#[allow(unused_variables)]
36+
#[tracing::instrument(skip_all)]
37+
async fn handle_error(
38+
&self,
39+
method: &::http::Method,
40+
host: &axum::extract::Host,
41+
cookies: &axum_extra::extract::CookieJar,
42+
error: E
43+
) -> Result<axum::response::Response, http::StatusCode> {
44+
tracing::error!("Unhandled error: {:?}", error);
45+
axum::response::Response::builder()
46+
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
47+
.body(axum::body::Body::empty())
48+
.map_err(|_| http::StatusCode::INTERNAL_SERVER_ERROR)
49+
}
50+
}

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

Lines changed: 15 additions & 15 deletions
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}}
@@ -31,49 +31,49 @@ pub trait {{classnamePascalCase}} {
3131
/// {{{operationId}}} - {{{httpMethod}}} {{{basePathWithoutHost}}}{{{path}}}
3232
async fn {{{x-operation-id}}}(
3333
&self,
34-
method: Method,
35-
host: Host,
36-
cookies: CookieJar,
34+
method: &Method,
35+
host: &Host,
36+
cookies: &CookieJar,
3737
{{#vendorExtensions}}
3838
{{#x-has-auth-methods}}
39-
claims: Self::Claims,
39+
claims: &Self::Claims,
4040
{{/x-has-auth-methods}}
4141
{{/vendorExtensions}}
4242
{{#headerParams.size}}
43-
header_params: models::{{{operationIdCamelCase}}}HeaderParams,
43+
header_params: &models::{{{operationIdCamelCase}}}HeaderParams,
4444
{{/headerParams.size}}
4545
{{#pathParams.size}}
46-
path_params: models::{{{operationIdCamelCase}}}PathParams,
46+
path_params: &models::{{{operationIdCamelCase}}}PathParams,
4747
{{/pathParams.size}}
4848
{{#queryParams.size}}
49-
query_params: models::{{{operationIdCamelCase}}}QueryParams,
49+
query_params: &models::{{{operationIdCamelCase}}}QueryParams,
5050
{{/queryParams.size}}
5151
{{^x-consumes-multipart-related}}
5252
{{^x-consumes-multipart}}
5353
{{#bodyParam}}
5454
{{#vendorExtensions}}
5555
{{^x-consumes-plain-text}}
56-
body: {{^required}}Option<{{/required}}{{{dataType}}}{{^required}}>{{/required}},
56+
body: &{{^required}}Option<{{/required}}{{{dataType}}}{{^required}}>{{/required}},
5757
{{/x-consumes-plain-text}}
5858
{{#x-consumes-plain-text}}
5959
{{#isString}}
60-
body: String,
60+
body: &String,
6161
{{/isString}}
6262
{{^isString}}
63-
body: Bytes,
63+
body: &Bytes,
6464
{{/isString}}
6565
{{/x-consumes-plain-text}}
6666
{{/vendorExtensions}}
6767
{{/bodyParam}}
6868
{{/x-consumes-multipart}}
6969
{{/x-consumes-multipart-related}}
7070
{{#x-consumes-multipart}}
71-
body: Multipart,
71+
body: &Multipart,
7272
{{/x-consumes-multipart}}
7373
{{#x-consumes-multipart-related}}
74-
body: axum::body::Body,
74+
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

Lines changed: 17 additions & 15 deletions
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
@@ -186,38 +187,38 @@ where
186187
{{/disableValidator}}
187188

188189
let result = api_impl.as_ref().{{#vendorExtensions}}{{{x-operation-id}}}{{/vendorExtensions}}(
189-
method,
190-
host,
191-
cookies,
190+
&method,
191+
&host,
192+
&cookies,
192193
{{#vendorExtensions}}
193194
{{#x-has-auth-methods}}
194-
claims,
195+
&claims,
195196
{{/x-has-auth-methods}}
196197
{{/vendorExtensions}}
197198
{{#headerParams.size}}
198-
header_params,
199+
&header_params,
199200
{{/headerParams.size}}
200201
{{#pathParams.size}}
201-
path_params,
202+
&path_params,
202203
{{/pathParams.size}}
203204
{{#queryParams.size}}
204-
query_params,
205+
&query_params,
205206
{{/queryParams.size}}
206207
{{#vendorExtensions}}
207208
{{^x-consumes-multipart-related}}
208209
{{^x-consumes-multipart}}
209210
{{#bodyParams}}
210211
{{#-first}}
211-
body,
212+
&body,
212213
{{/-first}}
213214
{{/bodyParams}}
214215
{{/x-consumes-multipart}}
215216
{{/x-consumes-multipart-related}}
216217
{{#x-consumes-multipart}}
217-
body,
218+
&body,
218219
{{/x-consumes-multipart}}
219220
{{#x-consumes-multipart-related}}
220-
body,
221+
&body,
221222
{{/x-consumes-multipart-related}}
222223
{{/vendorExtensions}}
223224
).await;
@@ -342,10 +343,11 @@ 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+
350+
return api_impl.as_ref().handle_error(&method, &host, &cookies, why).await;
349351
},
350352
};
351353

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

Lines changed: 4 additions & 3 deletions
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)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
7.11.0-SNAPSHOT
1+
7.12.0-SNAPSHOT

samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ tokio = { version = "1", default-features = false, features = [
4040
] }
4141
tracing = { version = "0.1", features = ["attributes"] }
4242
uuid = { version = "1", features = ["serde"] }
43-
validator = { version = "0.19", features = ["derive"] }
43+
validator = { version = "0.20", features = ["derive"] }
4444

4545
[dev-dependencies]
4646
tracing-subscriber = "0.3"

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ server, you can easily generate a server stub.
1212
To see how to make this your own, look here: [README]((https://openapi-generator.tech))
1313

1414
- API version: 1.0.0
15-
- Generator version: 7.11.0-SNAPSHOT
15+
- Generator version: 7.12.0-SNAPSHOT
1616

1717

1818

@@ -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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,23 @@ 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+
#[allow(unused_variables)]
32+
#[tracing::instrument(skip_all)]
33+
async fn handle_error(
34+
&self,
35+
method: &::http::Method,
36+
host: &axum::extract::Host,
37+
cookies: &axum_extra::extract::CookieJar,
38+
error: E,
39+
) -> Result<axum::response::Response, http::StatusCode> {
40+
tracing::error!("Unhandled error: {:?}", error);
41+
axum::response::Response::builder()
42+
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
43+
.body(axum::body::Body::empty())
44+
.map_err(|_| http::StatusCode::INTERNAL_SERVER_ERROR)
45+
}
46+
}

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

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,39 +38,41 @@ 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.
4547
///
4648
/// GetPaymentMethodById - GET /v71/paymentMethods/{id}
4749
async fn get_payment_method_by_id(
4850
&self,
49-
method: Method,
50-
host: Host,
51-
cookies: CookieJar,
52-
path_params: models::GetPaymentMethodByIdPathParams,
53-
) -> Result<GetPaymentMethodByIdResponse, ()>;
51+
method: &Method,
52+
host: &Host,
53+
cookies: &CookieJar,
54+
path_params: &models::GetPaymentMethodByIdPathParams,
55+
) -> Result<GetPaymentMethodByIdResponse, E>;
5456

5557
/// Get payment methods.
5658
///
5759
/// GetPaymentMethods - GET /v71/paymentMethods
5860
async fn get_payment_methods(
5961
&self,
60-
method: Method,
61-
host: Host,
62-
cookies: CookieJar,
63-
) -> Result<GetPaymentMethodsResponse, ()>;
62+
method: &Method,
63+
host: &Host,
64+
cookies: &CookieJar,
65+
) -> Result<GetPaymentMethodsResponse, E>;
6466

6567
/// Make a payment.
6668
///
6769
/// PostMakePayment - POST /v71/payments
6870
async fn post_make_payment(
6971
&self,
70-
method: Method,
71-
host: Host,
72-
cookies: CookieJar,
73-
claims: Self::Claims,
74-
body: Option<models::Payment>,
75-
) -> Result<PostMakePaymentResponse, ()>;
72+
method: &Method,
73+
host: &Host,
74+
cookies: &CookieJar,
75+
claims: &Self::Claims,
76+
body: &Option<models::Payment>,
77+
) -> Result<PostMakePaymentResponse, E>;
7678
}

0 commit comments

Comments
 (0)