pub const UNKNOWN_BLOCK_CODE: i64 = 3_100_002;
pub const OPTIONS_BODY: &[u8] = b"{}";
const ILLEGAL_TARGET: &str = r#"{"code":400,"message":"Illegal request-target","error":{"code":0,"name":"","what":"","details":[]}}"#;
const NOT_FOUND: &str = r#"{"code":404,"message":"Not Found","error":{"code":0,"name":"exception","what":"unspecified","details":[{"message":"Unknown Endpoint","file":"beast_http_session.hpp","line_number":186,"method":"handle_request"}]}}"#;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Busy {
InFlight(usize),
NoUpstream,
NoLiveUpstream,
LostAfterSend,
}
impl Busy {
pub fn what(self) -> String {
match self {
Busy::InFlight(n) => format!("Too many requests in flight: {n}"),
Busy::NoUpstream => "No chain API upstream available".to_string(),
Busy::NoLiveUpstream => "No live chain API upstream available".to_string(),
Busy::LostAfterSend => {
"Upstream connection lost after the request was sent".to_string()
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RouterError {
IllegalTarget,
NotFound,
TooLarge(usize),
AnswerTooLarge(u64),
Busy(Busy),
}
impl RouterError {
pub fn status(self) -> u16 {
match self {
RouterError::IllegalTarget => 400,
RouterError::NotFound => 404,
RouterError::TooLarge(_) => 413,
RouterError::AnswerTooLarge(_) => 502,
RouterError::Busy(_) => 503,
}
}
pub fn body(self) -> Vec<u8> {
match self {
RouterError::IllegalTarget => ILLEGAL_TARGET.as_bytes().to_vec(),
RouterError::NotFound => NOT_FOUND.as_bytes().to_vec(),
RouterError::TooLarge(limit) => short_error(
413,
"Payload Too Large",
&format!("Request body exceeds {limit} bytes"),
),
RouterError::AnswerTooLarge(limit) => short_error(
502,
"Bad Gateway",
&format!("Upstream answer exceeds {limit} bytes"),
),
RouterError::Busy(why) => short_error(503, "Busy", &why.what()),
}
}
}
fn short_error(code: u16, name: &str, what: &str) -> Vec<u8> {
let name = json_string(name);
format!(
r#"{{"code":{code},"message":{name},"error":{{"code":{code},"name":{name},"what":{},"details":[]}}}}"#,
json_string(what)
)
.into_bytes()
}
fn json_string(s: &str) -> String {
serde_json::to_string(s).expect("a string always serializes")
}
#[cfg(test)]
mod tests {
use super::*;
fn text(err: RouterError) -> String {
String::from_utf8(err.body()).unwrap()
}
#[test]
fn router_bodies_are_exact() {
assert_eq!(RouterError::IllegalTarget.status(), 400);
assert_eq!(
text(RouterError::IllegalTarget),
r#"{"code":400,"message":"Illegal request-target","error":{"code":0,"name":"","what":"","details":[]}}"#
);
assert_eq!(RouterError::NotFound.status(), 404);
assert_eq!(
text(RouterError::NotFound),
r#"{"code":404,"message":"Not Found","error":{"code":0,"name":"exception","what":"unspecified","details":[{"message":"Unknown Endpoint","file":"beast_http_session.hpp","line_number":186,"method":"handle_request"}]}}"#
);
assert_eq!(RouterError::TooLarge(2_097_152).status(), 413);
assert_eq!(
text(RouterError::TooLarge(2_097_152)),
r#"{"code":413,"message":"Payload Too Large","error":{"code":413,"name":"Payload Too Large","what":"Request body exceeds 2097152 bytes","details":[]}}"#
);
assert_eq!(RouterError::AnswerTooLarge(1_048_576).status(), 502);
assert_eq!(
text(RouterError::AnswerTooLarge(1_048_576)),
r#"{"code":502,"message":"Bad Gateway","error":{"code":502,"name":"Bad Gateway","what":"Upstream answer exceeds 1048576 bytes","details":[]}}"#
);
assert_eq!(OPTIONS_BODY, b"{}");
}
#[test]
fn busy_bodies_carry_the_reason() {
let cases = [
(
Busy::InFlight(5),
r#"{"code":503,"message":"Busy","error":{"code":503,"name":"Busy","what":"Too many requests in flight: 5","details":[]}}"#,
),
(
Busy::NoUpstream,
r#"{"code":503,"message":"Busy","error":{"code":503,"name":"Busy","what":"No chain API upstream available","details":[]}}"#,
),
(
Busy::NoLiveUpstream,
r#"{"code":503,"message":"Busy","error":{"code":503,"name":"Busy","what":"No live chain API upstream available","details":[]}}"#,
),
(
Busy::LostAfterSend,
r#"{"code":503,"message":"Busy","error":{"code":503,"name":"Busy","what":"Upstream connection lost after the request was sent","details":[]}}"#,
),
];
for (why, want) in cases {
assert_eq!(RouterError::Busy(why).status(), 503);
assert_eq!(text(RouterError::Busy(why)), want);
let parsed: serde_json::Value = serde_json::from_str(want).unwrap();
assert_eq!(parsed["error"]["what"], why.what());
}
}
}