Skip to content

Commit 5a56510

Browse files
committed
docs(http): extend http docs
1 parent f00eb3f commit 5a56510

File tree

6 files changed

+109
-23
lines changed

6 files changed

+109
-23
lines changed

crates/interledger-http/Cargo.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ interledger-service = { path = "../interledger-service", version = "^0.4.0", def
1515
log = { version = "0.4.8", default-features = false }
1616
reqwest = { version = "0.10.0", default-features = false, features = ["default-tls"] }
1717
url = { version = "2.1.0", default-features = false }
18-
# warp = { version = "0.1.20", default-features = false }
19-
warp = { git = "https://github.com/seanmonstar/warp.git" }
18+
warp = { version = "0.2", default-features = false }
2019
serde = { version = "1.0.101", default-features = false, features = ["derive"] }
2120
serde_json = { version = "1.0.41", default-features = false }
2221
serde_path_to_error = { version = "0.1", default-features = false }
@@ -25,7 +24,7 @@ chrono = { version = "0.4.9", features = ["clock"], default-features = false }
2524
regex = { version ="1.3.1", default-features = false, features = ["std"] }
2625
lazy_static = { version ="1.4.0", default-features = false }
2726
mime = { version ="0.3.14", default-features = false }
28-
secrecy = "0.5.2"
27+
secrecy = "0.6"
2928
async-trait = "0.1.22"
3029

3130
[dev-dependencies]

crates/interledger-http/src/client.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,20 @@ use reqwest::{
1212
use secrecy::{ExposeSecret, SecretString};
1313
use std::{convert::TryFrom, marker::PhantomData, sync::Arc, time::Duration};
1414

15+
/// The HttpClientService implements [OutgoingService](../../interledger_service/trait.OutgoingService)
16+
/// for sending ILP Prepare packets over to the HTTP URL associated with the provided account
17+
/// If no [ILP-over-HTTP](https://interledger.org/rfcs/0035-ilp-over-http) URL is specified for
18+
/// the account in the request, then it is forwarded to the next service.
1519
#[derive(Clone)]
1620
pub struct HttpClientService<S, O, A> {
21+
/// An HTTP client configured with a 30 second timeout by default. It is used to send the
22+
/// ILP over HTTP messages to the peer
1723
client: Client,
24+
/// The store used by the client to get the node's ILP Address,
25+
/// used to populate the `triggered_by` field in Reject packets
1826
store: Arc<S>,
27+
/// The next outgoing service to which non ILP-over-HTTP requests should
28+
/// be forwarded to
1929
next: O,
2030
account_type: PhantomData<A>,
2131
}
@@ -26,6 +36,7 @@ where
2636
O: OutgoingService<A> + Clone,
2737
A: HttpAccount,
2838
{
39+
/// Constructs the HttpClientService
2940
pub fn new(store: S, next: O) -> Self {
3041
let mut headers = HeaderMap::with_capacity(2);
3142
headers.insert(
@@ -103,6 +114,13 @@ where
103114
}
104115
}
105116

117+
/// Parses an ILP over HTTP response.
118+
///
119+
/// # Errors
120+
/// 1. If the response's status code is an error
121+
/// 1. If the response's body cannot be parsed as bytes
122+
/// 1. If the response's body is not a valid Packet (Fulfill or Reject)
123+
/// 1. If the packet is a Reject packet
106124
async fn parse_packet_from_response(response: HttpResponse, ilp_address: Address) -> IlpResult {
107125
let response = response.error_for_status().map_err(|err| {
108126
error!("HTTP error sending ILP over HTTP packet: {:?}", err);

crates/interledger-http/src/error/error_types.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,99 @@
1-
// APIs should implement their own `ApiErrorType`s to provide more detailed information
2-
// about what were the problem, for example, `JSON_SYNTAX_TYPE` or `ACCOUNT_NOT_FOUND_TYPE`.
3-
41
use super::{ApiError, ApiErrorType, ProblemType};
52
use http::StatusCode;
63
use lazy_static::lazy_static;
74

8-
// Default errors
5+
// Common HTTP errors
6+
97
#[allow(dead_code)]
8+
/// 400 Bad Request HTTP Status Code
109
pub const DEFAULT_BAD_REQUEST_TYPE: ApiErrorType = ApiErrorType {
1110
r#type: &ProblemType::Default,
1211
title: "Bad Request",
1312
status: StatusCode::BAD_REQUEST,
1413
};
14+
15+
/// 500 Internal Server Error HTTP Status Code
1516
pub const DEFAULT_INTERNAL_SERVER_ERROR_TYPE: ApiErrorType = ApiErrorType {
1617
r#type: &ProblemType::Default,
1718
title: "Internal Server Error",
1819
status: StatusCode::INTERNAL_SERVER_ERROR,
1920
};
21+
22+
/// 401 Unauthorized HTTP Status Code
2023
pub const DEFAULT_UNAUTHORIZED_TYPE: ApiErrorType = ApiErrorType {
2124
r#type: &ProblemType::Default,
2225
title: "Unauthorized",
2326
status: StatusCode::UNAUTHORIZED,
2427
};
28+
29+
/// 404 Not Found HTTP Status Code
2530
pub const DEFAULT_NOT_FOUND_TYPE: ApiErrorType = ApiErrorType {
2631
r#type: &ProblemType::Default,
2732
title: "Not Found",
2833
status: StatusCode::NOT_FOUND,
2934
};
35+
36+
/// 405 Method Not Allowed HTTP Status Code
3037
pub const DEFAULT_METHOD_NOT_ALLOWED_TYPE: ApiErrorType = ApiErrorType {
3138
r#type: &ProblemType::Default,
3239
title: "Method Not Allowed",
3340
status: StatusCode::METHOD_NOT_ALLOWED,
3441
};
42+
43+
/// 409 Conflict HTTP Status Code (used for Idempotency Conflicts)
3544
pub const DEFAULT_IDEMPOTENT_CONFLICT_TYPE: ApiErrorType = ApiErrorType {
3645
r#type: &ProblemType::Default,
3746
title: "Provided idempotency key is tied to other input",
3847
status: StatusCode::CONFLICT,
3948
};
4049

4150
// ILP over HTTP specific errors
51+
52+
/// ILP over HTTP invalid packet error type (400 Bad Request)
4253
pub const INVALID_ILP_PACKET_TYPE: ApiErrorType = ApiErrorType {
4354
r#type: &ProblemType::InterledgerHttpApi("ilp-over-http/invalid-packet"),
4455
title: "Invalid Packet",
4556
status: StatusCode::BAD_REQUEST,
4657
};
4758

48-
// JSON deserialization errors
59+
/// Wrong JSON syntax error type (400 Bad Request)
4960
pub const JSON_SYNTAX_TYPE: ApiErrorType = ApiErrorType {
5061
r#type: &ProblemType::InterledgerHttpApi("json-syntax"),
5162
title: "JSON Syntax Error",
5263
status: StatusCode::BAD_REQUEST,
5364
};
65+
66+
/// Wrong JSON data error type (400 Bad Request)
5467
pub const JSON_DATA_TYPE: ApiErrorType = ApiErrorType {
5568
r#type: &ProblemType::InterledgerHttpApi("json-data"),
5669
title: "JSON Data Error",
5770
status: StatusCode::BAD_REQUEST,
5871
};
72+
73+
/// JSON EOF error type (400 Bad Request)
5974
pub const JSON_EOF_TYPE: ApiErrorType = ApiErrorType {
6075
r#type: &ProblemType::InterledgerHttpApi("json-eof"),
6176
title: "JSON Unexpected EOF",
6277
status: StatusCode::BAD_REQUEST,
6378
};
79+
80+
/// JSON IO error type (400 Bad Request)
6481
pub const JSON_IO_TYPE: ApiErrorType = ApiErrorType {
6582
r#type: &ProblemType::InterledgerHttpApi("json-io"),
6683
title: "JSON IO Error",
6784
status: StatusCode::BAD_REQUEST,
6885
};
6986

7087
// Account specific errors
88+
89+
/// Account Not Found error type (404 Not Found)
7190
pub const ACCOUNT_NOT_FOUND_TYPE: ApiErrorType = ApiErrorType {
7291
r#type: &ProblemType::InterledgerHttpApi("accounts/account-not-found"),
7392
title: "Account Not Found",
7493
status: StatusCode::NOT_FOUND,
7594
};
7695

77-
// Node settings specific errors
96+
/// Invalid Account Id error type (400 Bad Request)
7897
pub const INVALID_ACCOUNT_ID_TYPE: ApiErrorType = ApiErrorType {
7998
r#type: &ProblemType::InterledgerHttpApi("settings/invalid-account-id"),
8099
title: "Invalid Account Id",
@@ -84,14 +103,17 @@ pub const INVALID_ACCOUNT_ID_TYPE: ApiErrorType = ApiErrorType {
84103
// String used for idempotency errors
85104
pub static IDEMPOTENCY_CONFLICT_ERR: &str = "Provided idempotency key is tied to other input";
86105

87-
// Idempotency errors
106+
/// 409 Conflict HTTP Status Code (used for Idempotency Conflicts)
107+
// TODO: Remove this since it is a duplicate of DEFAULT_IDEMPOTENT_CONFLICT_TYPE
88108
pub const IDEMPOTENT_STORE_CALL_ERROR_TYPE: ApiErrorType = ApiErrorType {
89109
r#type: &ProblemType::Default,
90110
title: "Store idempotency error",
91111
status: StatusCode::CONFLICT,
92112
};
93113

94114
lazy_static! {
115+
/// Error which must be returned when the same idempotency key is
116+
/// used for more than 1 input
95117
pub static ref IDEMPOTENT_STORE_CALL_ERROR: ApiError =
96118
ApiError::from_api_error_type(&IDEMPOTENT_STORE_CALL_ERROR_TYPE)
97119
.detail("Could not process idempotent data in store");

crates/interledger-http/src/error/mod.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/// APIs should implement their own `ApiErrorType`s to provide more detailed information
2+
/// about what were the problem, for example, `JSON_SYNTAX_TYPE` or `ACCOUNT_NOT_FOUND_TYPE`.
13
mod error_types;
24
pub use error_types::*;
35

@@ -70,6 +72,7 @@ pub struct ApiError {
7072
pub extension_members: Option<Map<String, Value>>,
7173
}
7274

75+
/// Distinguishes between RFC7807 and Interledger API Errors
7376
#[derive(Clone, Copy, Debug)]
7477
pub enum ProblemType {
7578
/// `Default` is a [pre-defined value](https://tools.ietf.org/html/rfc7807#section-4.2) which is
@@ -81,10 +84,14 @@ pub enum ProblemType {
8184
InterledgerHttpApi(&'static str),
8285
}
8386

87+
/// Error type used as a basis for creating Warp-compatible Errors
8488
#[derive(Clone, Copy, Debug)]
8589
pub struct ApiErrorType {
90+
/// Interledger or RFC7807 Error
8691
pub r#type: &'static ProblemType,
92+
/// The Title to be used for the error page
8793
pub title: &'static str,
94+
/// The HTTP Status Code for the error
8895
pub status: http::StatusCode,
8996
}
9097

@@ -110,6 +117,7 @@ where
110117
}
111118

112119
impl ApiError {
120+
/// Constructs an API Error from a [ApiErrorType](./struct.ApiErrorType.html)
113121
pub fn from_api_error_type(problem_type: &ApiErrorType) -> Self {
114122
ApiError {
115123
r#type: problem_type.r#type,
@@ -123,39 +131,49 @@ impl ApiError {
123131

124132
// Note that we should basically avoid using the following default errors because
125133
// we should provide more detailed information for developers
134+
126135
#[allow(dead_code)]
136+
/// Returns a Bad Request [ApiError](./struct.ApiError.html)
127137
pub fn bad_request() -> Self {
128138
ApiError::from_api_error_type(&DEFAULT_BAD_REQUEST_TYPE)
129139
}
130140

141+
/// Returns an Internal Server Error [ApiError](./struct.ApiError.html)
131142
pub fn internal_server_error() -> Self {
132143
ApiError::from_api_error_type(&DEFAULT_INTERNAL_SERVER_ERROR_TYPE)
133144
}
134145

146+
/// Returns an Unauthorized [ApiError](./struct.ApiError.html)
135147
pub fn unauthorized() -> Self {
136148
ApiError::from_api_error_type(&DEFAULT_UNAUTHORIZED_TYPE)
137149
}
138150

139151
#[allow(dead_code)]
152+
/// Returns an Error Not Found [ApiError](./struct.ApiError.html)
140153
pub fn not_found() -> Self {
141154
ApiError::from_api_error_type(&DEFAULT_NOT_FOUND_TYPE)
142155
}
143156

144157
#[allow(dead_code)]
158+
/// Returns a Method Not Found [ApiError](./struct.ApiError.html)
145159
pub fn method_not_allowed() -> Self {
146160
ApiError::from_api_error_type(&DEFAULT_METHOD_NOT_ALLOWED_TYPE)
147161
}
148162

163+
/// Returns an Account not Found [ApiError](./struct.ApiError.html)
149164
pub fn account_not_found() -> Self {
150165
ApiError::from_api_error_type(&ACCOUNT_NOT_FOUND_TYPE)
151166
.detail("Username was not found.".to_owned())
152167
}
153168

154169
#[allow(dead_code)]
170+
/// Returns an Idempotency Conflict [ApiError](./struct.ApiError.html)
171+
/// via the [default idempotency conflict ApiErrorType](./error_types/constant.DEFAULT_IDEMPOTENT_CONFLICT_TYPE.html)
155172
pub fn idempotency_conflict() -> Self {
156173
ApiError::from_api_error_type(&DEFAULT_IDEMPOTENT_CONFLICT_TYPE)
157174
}
158175

176+
/// Returns an Invalid Account Id [ApiError](./struct.ApiError.html)
159177
pub fn invalid_account_id(invalid_account_id: Option<&str>) -> Self {
160178
let detail = match invalid_account_id {
161179
Some(invalid_account_id) => match invalid_account_id.len() {
@@ -167,10 +185,12 @@ impl ApiError {
167185
ApiError::from_api_error_type(&INVALID_ACCOUNT_ID_TYPE).detail(detail)
168186
}
169187

188+
/// Returns an Invalid ILP over HTTP [ApiError](./struct.ApiError.html)
170189
pub fn invalid_ilp_packet() -> Self {
171190
ApiError::from_api_error_type(&INVALID_ILP_PACKET_TYPE)
172191
}
173192

193+
/// Sets the [`detail`](./struct.ApiError.html#structfield.detail) field
174194
pub fn detail<T>(mut self, detail: T) -> Self
175195
where
176196
T: Into<String>,
@@ -180,6 +200,7 @@ impl ApiError {
180200
}
181201

182202
#[allow(dead_code)]
203+
/// Sets the [`instance`](./struct.ApiError.html#structfield.instance) field
183204
pub fn instance<T>(mut self, instance: T) -> Self
184205
where
185206
T: Into<String>,
@@ -188,8 +209,9 @@ impl ApiError {
188209
self
189210
}
190211

191-
pub fn extension_members(mut self, extension_members: Option<Map<String, Value>>) -> Self {
192-
self.extension_members = extension_members;
212+
/// Sets the [`extension_members`](./struct.ApiError.html#structfield.extension_members) field
213+
pub fn extension_members(mut self, extension_members: Map<String, Value>) -> Self {
214+
self.extension_members = Some(extension_members);
193215
self
194216
}
195217

@@ -298,15 +320,14 @@ impl Reply for JsonDeserializeError {
298320
Category::Io => &JSON_IO_TYPE,
299321
};
300322
let detail = self.detail;
301-
let extension_members = match extension_members.keys().len() {
302-
0 => None,
303-
_ => Some(extension_members),
304-
};
305323

306-
ApiError::from_api_error_type(api_error_type)
307-
.detail(detail)
308-
.extension_members(extension_members)
309-
.into_response()
324+
let mut error = ApiError::from_api_error_type(api_error_type).detail(detail);
325+
326+
if extension_members.keys().len() > 0 {
327+
error = error.extension_members(extension_members);
328+
}
329+
330+
error.into_response()
310331
}
311332
}
312333

crates/interledger-http/src/lib.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,21 @@ use serde::de::DeserializeOwned;
1111
use url::Url;
1212
use warp::{self, Filter, Rejection};
1313

14+
/// [ILP over HTTP](https://interledger.org/rfcs/0035-ilp-over-http/) Outgoing Service
1415
mod client;
15-
mod server;
16-
17-
// So that settlement engines can use errors
16+
/// [RFC7807](https://tools.ietf.org/html/rfc7807) compliant errors
1817
pub mod error;
18+
/// [ILP over HTTP](https://interledger.org/rfcs/0035-ilp-over-http/) API (implemented with [Warp](https://docs.rs/warp/0.2.0/warp/))
19+
mod server;
1920

2021
pub use self::client::HttpClientService;
2122
pub use self::server::HttpServer;
2223

24+
/// Extention trait for [Account](../interledger_service/trait.Account.html) with [ILP over HTTP](https://interledger.org/rfcs/0035-ilp-over-http/) related information
2325
pub trait HttpAccount: Account {
26+
/// Returns the HTTP URL corresponding to this account
2427
fn get_http_url(&self) -> Option<&Url>;
28+
/// Returns the HTTP token which is sent as an HTTP header on each ILP over HTTP request
2529
fn get_http_auth_token(&self) -> Option<SecretString>;
2630
}
2731

@@ -42,6 +46,9 @@ pub trait HttpStore: Clone + Send + Sync + 'static {
4246

4347
// TODO: Do we really need this custom deserialization function?
4448
// You'd expect that Serde would be able to handle this.
49+
/// Helper function to deserialize JSON inside Warp
50+
/// The content-type MUST be application/json and if a charset
51+
/// is specified, it MUST be UTF-8
4552
pub fn deserialize_json<T: DeserializeOwned + Send>(
4653
) -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
4754
warp::header::<String>("content-type")

0 commit comments

Comments
 (0)