Skip to content

Commit 319d675

Browse files
committed
Use Rust types to force IntoTwirpResponse to return a valid body.
It's still possible to return a response with an invalid status code or headers, but less likely to happen by accident.
1 parent 6fd41bb commit 319d675

File tree

3 files changed

+21
-20
lines changed

3 files changed

+21
-20
lines changed

crates/twirp/src/error.rs

+16-12
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ pub trait IntoTwirpResponse {
1717
/// ```
1818
/// use axum::body::Body;
1919
/// use http::Response;
20-
/// use twirp::IntoTwirpResponse;
20+
/// use twirp::{TwirpErrorResponse, IntoTwirpResponse};
2121
/// # struct MyError { message: String }
2222
///
2323
/// impl IntoTwirpResponse for MyError {
24-
/// fn into_twirp_response(self) -> Response<Body> {
24+
/// fn into_twirp_response(self) -> Response<TwirpErrorResponse> {
2525
/// // Use TwirpErrorResponse to generate a valid starting point
2626
/// let mut response = twirp::invalid_argument(&self.message)
2727
/// .into_twirp_response();
@@ -35,7 +35,7 @@ pub trait IntoTwirpResponse {
3535
///
3636
/// The `Response` that `TwirpErrorResponse` generates can be used as a starting point,
3737
/// adding headers and extensions to it.
38-
fn into_twirp_response(self) -> Response<Body>;
38+
fn into_twirp_response(self) -> Response<TwirpErrorResponse>;
3939
}
4040

4141
/// Alias for a generic error
@@ -182,26 +182,30 @@ impl TwirpErrorResponse {
182182
pub fn insert_meta(&mut self, key: String, value: String) -> Option<String> {
183183
self.meta.insert(key, value)
184184
}
185-
}
186185

187-
impl IntoTwirpResponse for TwirpErrorResponse {
188-
fn into_twirp_response(self) -> Response<Body> {
189-
IntoResponse::into_response(self)
186+
pub fn into_axum_body(self) -> Body {
187+
let json =
188+
serde_json::to_string(&self).expect("JSON serialization of an error should not fail");
189+
Body::new(json)
190190
}
191191
}
192192

193-
impl IntoResponse for TwirpErrorResponse {
194-
fn into_response(self) -> Response<Body> {
193+
impl IntoTwirpResponse for TwirpErrorResponse {
194+
fn into_twirp_response(self) -> Response<TwirpErrorResponse> {
195195
let mut headers = HeaderMap::new();
196196
headers.insert(
197197
header::CONTENT_TYPE,
198198
HeaderValue::from_static("application/json"),
199199
);
200200

201-
let json =
202-
serde_json::to_string(&self).expect("JSON serialization of an error should not fail");
201+
let code = self.code.http_status_code();
202+
(code, headers).into_response().map(|_| self)
203+
}
204+
}
203205

204-
(self.code.http_status_code(), headers, json).into_response()
206+
impl IntoResponse for TwirpErrorResponse {
207+
fn into_response(self) -> Response<Body> {
208+
self.into_twirp_response().map(|err| err.into_axum_body())
205209
}
206210
}
207211

crates/twirp/src/server.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ where
7070
// .insert(RequestError(err));
7171
let mut twirp_err = error::malformed("bad request");
7272
twirp_err.insert_meta("error".to_string(), err.to_string());
73-
return twirp_err.into_twirp_response();
73+
return twirp_err.into_response();
7474
}
7575
};
7676

@@ -85,7 +85,7 @@ where
8585
// TODO: Capture original error in the response extensions.
8686
let mut twirp_err = error::unknown("error serializing response");
8787
twirp_err.insert_meta("error".to_string(), err.to_string());
88-
return twirp_err.into_twirp_response();
88+
return twirp_err.into_response();
8989
}
9090
};
9191
timings.set_response_written();
@@ -135,7 +135,7 @@ where
135135
.body(Body::from(data))?
136136
}
137137
},
138-
Err(err) => err.into_twirp_response(),
138+
Err(err) => err.into_response(),
139139
};
140140
Ok(res)
141141
}

example/src/bin/advanced-server.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use twirp::axum::body::Body;
88
use twirp::axum::http;
99
use twirp::axum::middleware::{self, Next};
1010
use twirp::axum::routing::get;
11-
use twirp::{invalid_argument, Context, IntoTwirpResponse, Router};
11+
use twirp::{invalid_argument, Context, IntoTwirpResponse, Router, TwirpErrorResponse};
1212

1313
pub mod service {
1414
pub mod haberdash {
@@ -56,10 +56,7 @@ enum HatError {
5656
}
5757

5858
impl IntoTwirpResponse for HatError {
59-
fn into_twirp_response(self) -> http::Response<Body> {
60-
// Note: When converting a HatError to a response, since we want the server to be a twirp
61-
// server, it's important to generate a response that follows the twirp standard. We do
62-
// that here by using TwirpErrorResponse.
59+
fn into_twirp_response(self) -> http::Response<TwirpErrorResponse> {
6360
match self {
6461
HatError::InvalidSize => invalid_argument("inches").into_twirp_response(),
6562
}

0 commit comments

Comments
 (0)