Skip to content
This repository was archived by the owner on Mar 23, 2021. It is now read-only.

Commit e7955c8

Browse files
Merge #1697
1697: Simplify error handling in http routes r=thomaseizinger a=thomaseizinger By making better use of anyhow and thiserror, we can question-mark away many of the errors. We still have control of the error returned to the user through the custom recover function of warp. Fixes #1683. Co-authored-by: Thomas Eizinger <[email protected]>
2 parents e14f681 + 9a34567 commit e7955c8

File tree

12 files changed

+286
-265
lines changed

12 files changed

+286
-265
lines changed

cnd/src/http_api/action.rs

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::{
2-
http_api::{ethereum_network, problem, Http},
2+
http_api::{
3+
ethereum_network, problem, Http, MissingQueryParameters, UnexpectedQueryParameters,
4+
},
35
swap_protocols::{
46
actions::{
57
bitcoin::{SendToAddress, SpendOutput},
@@ -8,6 +10,7 @@ use crate::{
810
ledger, SwapId, Timestamp,
911
},
1012
};
13+
use anyhow::Context;
1114
use blockchain_contracts::bitcoin::witness;
1215
use http::StatusCode;
1316
use http_api_problem::HttpApiProblem;
@@ -94,20 +97,20 @@ pub trait IntoResponsePayload {
9497
fn into_response_payload(
9598
self,
9699
parameters: ActionExecutionParameters,
97-
) -> Result<ActionResponseBody, HttpApiProblem>;
100+
) -> anyhow::Result<ActionResponseBody>;
98101
}
99102

100103
impl IntoResponsePayload for SendToAddress {
101104
fn into_response_payload(
102105
self,
103106
query_params: ActionExecutionParameters,
104-
) -> Result<ActionResponseBody, HttpApiProblem> {
107+
) -> anyhow::Result<ActionResponseBody> {
105108
match query_params {
106109
ActionExecutionParameters::None {} => Ok(self.into()),
107-
_ => Err(problem::unexpected_query_parameters(
108-
"bitcoin::SendToAddress",
109-
vec!["address".into(), "fee_per_wu".into()],
110-
)),
110+
_ => Err(anyhow::Error::from(UnexpectedQueryParameters {
111+
action: "bitcoin::SendToAddress",
112+
parameters: &["address", "fee_per_wu"],
113+
})),
111114
}
112115
}
113116
}
@@ -137,13 +140,13 @@ impl IntoResponsePayload for SpendOutput {
137140
fn into_response_payload(
138141
self,
139142
query_params: ActionExecutionParameters,
140-
) -> Result<ActionResponseBody, HttpApiProblem> {
143+
) -> anyhow::Result<ActionResponseBody> {
141144
match query_params {
142145
ActionExecutionParameters::BitcoinAddressAndFee {
143146
address,
144147
fee_per_wu,
145148
} => {
146-
let fee_per_wu = fee_per_wu.parse::<usize>().map_err(|_| {
149+
let fee_per_wu = fee_per_wu.parse::<usize>().with_context(|| {
147150
HttpApiProblem::new("Invalid query parameter.")
148151
.set_status(StatusCode::BAD_REQUEST)
149152
.set_detail("Query parameter fee-per-byte is not a valid unsigned integer.")
@@ -178,22 +181,22 @@ impl IntoResponsePayload for SpendOutput {
178181
network,
179182
))
180183
}
181-
_ => Err(problem::missing_query_parameters(
182-
"bitcoin::SpendOutput",
183-
vec![
184-
&problem::MissingQueryParameter {
184+
_ => Err(anyhow::Error::from(MissingQueryParameters {
185+
action: "bitcoin::SpendOutput",
186+
parameters: &[
187+
problem::MissingQueryParameter {
185188
name: "address",
186189
data_type: "string",
187190
description: "The bitcoin address to where the funds should be sent.",
188191
},
189-
&problem::MissingQueryParameter {
192+
problem::MissingQueryParameter {
190193
name: "fee_per_wu",
191194
data_type: "uint",
192195
description:
193196
"The fee per weight unit you want to pay for the transaction in satoshis.",
194197
},
195-
],
196-
)),
198+
]
199+
}))
197200
}
198201
}
199202
}
@@ -229,7 +232,7 @@ impl IntoResponsePayload for ethereum::DeployContract {
229232
fn into_response_payload(
230233
self,
231234
query_params: ActionExecutionParameters,
232-
) -> Result<ActionResponseBody, HttpApiProblem> {
235+
) -> anyhow::Result<ActionResponseBody> {
233236
let ethereum::DeployContract {
234237
data,
235238
amount,
@@ -244,10 +247,10 @@ impl IntoResponsePayload for ethereum::DeployContract {
244247
chain_id,
245248
network: chain_id.into(),
246249
}),
247-
_ => Err(problem::unexpected_query_parameters(
248-
"ethereum::ContractDeploy",
249-
vec!["address".into(), "fee_per_wu".into()],
250-
)),
250+
_ => Err(anyhow::Error::from(UnexpectedQueryParameters {
251+
action: "ethereum::ContractDeploy",
252+
parameters: &["address", "fee_per_wu"],
253+
})),
251254
}
252255
}
253256
}
@@ -262,7 +265,7 @@ impl IntoResponsePayload for ethereum::CallContract {
262265
fn into_response_payload(
263266
self,
264267
query_params: ActionExecutionParameters,
265-
) -> Result<ActionResponseBody, HttpApiProblem> {
268+
) -> anyhow::Result<ActionResponseBody> {
266269
let ethereum::CallContract {
267270
to,
268271
data,
@@ -279,10 +282,10 @@ impl IntoResponsePayload for ethereum::CallContract {
279282
network: chain_id.into(),
280283
min_block_timestamp,
281284
}),
282-
_ => Err(problem::unexpected_query_parameters(
283-
"ethereum::SendTransaction",
284-
vec!["address".into(), "fee_per_wu".into()],
285-
)),
285+
_ => Err(anyhow::Error::from(UnexpectedQueryParameters {
286+
action: "ethereum::SendTransaction",
287+
parameters: &["address", "fee_per_wu"],
288+
})),
286289
}
287290
}
288291
}
@@ -303,7 +306,7 @@ impl IntoResponsePayload for Infallible {
303306
fn into_response_payload(
304307
self,
305308
_: ActionExecutionParameters,
306-
) -> Result<ActionResponseBody, HttpApiProblem> {
309+
) -> anyhow::Result<ActionResponseBody> {
307310
unreachable!("how did you manage to construct Infallible?")
308311
}
309312
}

cnd/src/http_api/problem.rs

Lines changed: 77 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,149 +1,118 @@
11
use crate::{
22
db,
3-
swap_protocols::rfc003::{self, actions::ActionKind, state_store},
3+
http_api::routes::rfc003::handlers::{
4+
post_swap::{MalformedRequest, UnsupportedSwap},
5+
InvalidAction, InvalidActionInvocation,
6+
},
47
};
58
use http::StatusCode;
69
use http_api_problem::HttpApiProblem;
7-
use libp2p_comit::frame::Response;
8-
use serde::Serialize;
910
use warp::{Rejection, Reply};
1011

11-
#[derive(Debug, Serialize)]
12+
#[derive(Debug, thiserror::Error)]
13+
#[error("Missing GET parameters for a {} action type. Expected: {:?}", action, parameters.iter().map(|parameter| parameter.name).collect::<Vec<&str>>())]
14+
pub struct MissingQueryParameters {
15+
pub action: &'static str,
16+
pub parameters: &'static [MissingQueryParameter],
17+
}
18+
19+
#[derive(Debug, serde::Serialize)]
1220
pub struct MissingQueryParameter {
1321
pub name: &'static str,
1422
pub data_type: &'static str,
1523
pub description: &'static str,
1624
}
1725

26+
#[derive(Debug, thiserror::Error)]
27+
#[error("unexpected GET parameters {parameters:?} for a {action} action type, expected: none")]
28+
pub struct UnexpectedQueryParameters {
29+
pub action: &'static str,
30+
pub parameters: &'static [&'static str],
31+
}
32+
1833
pub fn from_anyhow(e: anyhow::Error) -> HttpApiProblem {
34+
let e = match e.downcast::<HttpApiProblem>() {
35+
Ok(problem) => return problem,
36+
Err(e) => e,
37+
};
38+
1939
if let Some(db::Error::SwapNotFound) = e.downcast_ref::<db::Error>() {
20-
return swap_not_found();
40+
return HttpApiProblem::new("Swap not found.").set_status(StatusCode::NOT_FOUND);
2141
}
2242

23-
internal_error(e)
24-
}
43+
if let Some(e) = e.downcast_ref::<UnexpectedQueryParameters>() {
44+
log::error!("{}", e);
2545

26-
pub fn internal_error(e: anyhow::Error) -> HttpApiProblem {
27-
log::error!("internal error occurred: {:?}", e);
28-
HttpApiProblem::with_title_and_type_from_status(StatusCode::INTERNAL_SERVER_ERROR)
29-
}
46+
let mut problem = HttpApiProblem::new("Unexpected query parameter(s).")
47+
.set_status(StatusCode::BAD_REQUEST)
48+
.set_detail("This action does not take any query parameters.");
3049

31-
pub fn missing_channel() -> HttpApiProblem {
32-
log::error!("Channel for swap was not found in hash map");
33-
HttpApiProblem::with_title_and_type_from_status(StatusCode::INTERNAL_SERVER_ERROR)
34-
}
50+
problem
51+
.set_value("unexpected_parameters", &e.parameters)
52+
.expect("parameters will never fail to serialize");
3553

36-
pub fn send_over_channel(_e: Response) -> HttpApiProblem {
37-
log::error!("Sending response over channel failed");
38-
HttpApiProblem::with_title_and_type_from_status(StatusCode::INTERNAL_SERVER_ERROR)
39-
}
54+
return problem;
55+
}
4056

41-
pub fn state_store() -> HttpApiProblem {
42-
log::error!("State store didn't have state in it despite swap being in database");
43-
HttpApiProblem::with_title_and_type_from_status(StatusCode::INTERNAL_SERVER_ERROR)
44-
}
57+
if let Some(e) = e.downcast_ref::<MissingQueryParameters>() {
58+
log::error!("{}", e);
4559

46-
pub fn swap_not_found() -> HttpApiProblem {
47-
HttpApiProblem::new("Swap not found.").set_status(StatusCode::NOT_FOUND)
48-
}
60+
let mut problem = HttpApiProblem::new("Missing query parameter(s).")
61+
.set_status(StatusCode::BAD_REQUEST)
62+
.set_detail("This action requires additional query parameters.");
4963

50-
pub fn unsupported() -> HttpApiProblem {
51-
HttpApiProblem::new("Swap not supported.")
52-
.set_status(StatusCode::BAD_REQUEST)
53-
.set_detail("The requested combination of ledgers and assets is not supported.")
54-
}
64+
problem
65+
.set_value("missing_parameters", &e.parameters)
66+
.expect("parameters will never fail to serialize");
5567

56-
pub fn deserialize(e: serde_json::Error) -> HttpApiProblem {
57-
log::error!("Failed to deserialize body: {:?}", e);
58-
HttpApiProblem::new("Invalid body.")
59-
.set_status(StatusCode::BAD_REQUEST)
60-
.set_detail("Failed to deserialize given body.")
61-
}
68+
return problem;
69+
}
6270

63-
pub fn serialize(e: serde_json::Error) -> HttpApiProblem {
64-
log::error!("Failed to serialize body: {:?}", e);
65-
HttpApiProblem::with_title_and_type_from_status(StatusCode::INTERNAL_SERVER_ERROR)
66-
}
71+
if e.is::<serde_json::Error>() {
72+
log::error!("deserialization error: {:?}", e);
6773

68-
pub fn not_yet_implemented(feature: &str) -> HttpApiProblem {
69-
log::error!("{} not yet implemented", feature);
70-
HttpApiProblem::new("Feature not yet implemented.")
71-
.set_status(StatusCode::INTERNAL_SERVER_ERROR)
72-
.set_detail(format!("{} is not yet implemented! Sorry :(", feature))
73-
}
74+
return HttpApiProblem::new("Invalid body.")
75+
.set_status(StatusCode::BAD_REQUEST)
76+
.set_detail("Failed to deserialize given body.");
77+
}
7478

75-
pub fn action_already_done(action: ActionKind) -> HttpApiProblem {
76-
log::error!("{} action has already been done", action);
77-
HttpApiProblem::new("Action already done.").set_status(StatusCode::GONE)
78-
}
79+
if e.is::<InvalidActionInvocation>() {
80+
log::warn!("{:?}", e);
7981

80-
pub fn invalid_action(action: ActionKind) -> HttpApiProblem {
81-
log::error!("{} action is invalid for this swap", action);
82-
HttpApiProblem::new("Invalid action.")
83-
.set_status(StatusCode::CONFLICT)
84-
.set_detail("Cannot perform requested action for this swap.")
85-
}
82+
return HttpApiProblem::new("Invalid action invocation")
83+
.set_status(http::StatusCode::METHOD_NOT_ALLOWED);
84+
}
8685

87-
pub fn unexpected_query_parameters(action: &str, parameters: Vec<String>) -> HttpApiProblem {
88-
log::error!(
89-
"Unexpected GET parameters {:?} for a {} action type. Expected: none",
90-
parameters,
91-
action
92-
);
93-
let mut problem = HttpApiProblem::new("Unexpected query parameter(s).")
94-
.set_status(StatusCode::BAD_REQUEST)
95-
.set_detail("This action does not take any query parameters.");
96-
97-
problem
98-
.set_value("unexpected_parameters", &parameters)
99-
.expect("parameters will never fail to serialize");
100-
101-
problem
102-
}
86+
if e.is::<InvalidAction>() {
87+
log::warn!("{:?}", e);
10388

104-
pub fn missing_query_parameters(
105-
action: &str,
106-
parameters: Vec<&MissingQueryParameter>,
107-
) -> HttpApiProblem {
108-
log::error!(
109-
"Missing GET parameters for a {} action type. Expected: {:?}",
110-
action,
111-
parameters
112-
.iter()
113-
.map(|parameter| parameter.name)
114-
.collect::<Vec<&str>>()
115-
);
116-
117-
let mut problem = HttpApiProblem::new("Missing query parameter(s).")
118-
.set_status(StatusCode::BAD_REQUEST)
119-
.set_detail("This action requires additional query parameters.");
120-
121-
problem
122-
.set_value("missing_parameters", &parameters)
123-
.expect("parameters will never fail to serialize");
124-
125-
problem
126-
}
89+
return HttpApiProblem::new("Invalid action.")
90+
.set_status(StatusCode::CONFLICT)
91+
.set_detail("Cannot perform requested action for this swap.");
92+
}
93+
94+
if e.is::<UnsupportedSwap>() {
95+
log::warn!("{:?}", e);
12796

128-
impl From<state_store::Error> for HttpApiProblem {
129-
fn from(e: state_store::Error) -> Self {
130-
log::error!("Storage layer failure: {:?}", e);
131-
HttpApiProblem::with_title_and_type_from_status(StatusCode::INTERNAL_SERVER_ERROR)
97+
return HttpApiProblem::new("Swap not supported.")
98+
.set_status(StatusCode::BAD_REQUEST)
99+
.set_detail("The requested combination of ledgers and assets is not supported.");
132100
}
133-
}
134101

135-
impl From<rfc003::state_machine::Error> for HttpApiProblem {
136-
fn from(e: rfc003::state_machine::Error) -> Self {
137-
log::error!("Protocol execution error: {:?}", e);
138-
HttpApiProblem::with_title_and_type_from_status(StatusCode::INTERNAL_SERVER_ERROR)
139-
.set_title("Protocol execution error.")
102+
if e.is::<MalformedRequest>() {
103+
log::warn!("{:?}", e);
104+
105+
return HttpApiProblem::with_title_and_type_from_status(StatusCode::BAD_REQUEST)
106+
.set_detail("The request body was malformed.");
140107
}
108+
109+
log::error!("internal error occurred: {:?}", e);
110+
111+
HttpApiProblem::with_title_and_type_from_status(StatusCode::INTERNAL_SERVER_ERROR)
141112
}
142113

143114
pub fn unpack_problem(rejection: Rejection) -> Result<impl Reply, Rejection> {
144115
if let Some(problem) = rejection.find_cause::<HttpApiProblem>() {
145-
log::debug!(target: "http-api", "HTTP request got rejected, returning HttpApiProblem response: {:?}", problem);
146-
147116
let code = problem.status.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
148117

149118
let reply = warp::reply::json(problem);

0 commit comments

Comments
 (0)