use std::time::Duration;
use polyoxide_core::{truncate_for_log, ApiError, ErrorResponse};
use polyoxide_venue::{class_for_status, Class};
use serde::Deserialize;
use crate::shared::weight::MAX_COOLDOWN;
#[derive(Debug)]
pub(crate) enum Decoded {
Api(ApiError),
Venue {
status: u16,
code: i64,
msg: String,
},
RateLimited {
retry_after: Option<Duration>,
},
IpBanned {
retry_after: Option<Duration>,
body: String,
},
RegionBlocked {
msg: String,
},
Forbidden {
msg: String,
},
}
#[derive(Deserialize)]
struct VenueBody {
code: i64,
msg: String,
}
pub(crate) fn decode(err: ApiError) -> Decoded {
match err {
ApiError::Response(response) => from_response(*response),
other => Decoded::Api(other),
}
}
fn from_response(mut response: ErrorResponse) -> Decoded {
let status = response.status.as_u16();
let retry_after = retry_after_secs(
response
.headers
.get(polyoxide_core::http::header::RETRY_AFTER)
.and_then(|value| value.to_str().ok()),
);
match status {
418 => Decoded::IpBanned {
retry_after,
body: response.body,
},
429 => Decoded::RateLimited { retry_after },
451 => Decoded::RegionBlocked {
msg: clip(&response.body),
},
403 => Decoded::Forbidden {
msg: clip(&response.body),
},
_ => match serde_json::from_str::<VenueBody>(&response.body) {
Ok(venue) => Decoded::Venue {
status,
code: venue.code,
msg: clip(&venue.msg),
},
Err(_) => {
response.message = clip(&response.message);
response.body = clip(&response.body);
response.retry_after = retry_after;
Decoded::Api(response.into())
}
},
}
}
pub(crate) fn retry_after_secs(value: Option<&str>) -> Option<Duration> {
value.and_then(|value| polyoxide_venue::parse_retry_after(value, MAX_COOLDOWN))
}
pub(crate) fn clip(text: &str) -> String {
truncate_for_log(text).into_owned()
}
pub(crate) fn venue_class(status: u16, code: i64) -> Class {
class_for_status(status)
.unwrap_or(Class::Decode)
.with_code(code.to_string())
}
#[cfg(test)]
mod tests {
use polyoxide_core::http::header::HeaderMap;
use polyoxide_core::http::StatusCode;
use super::*;
#[test]
fn a_418_hands_its_product_the_whole_body() {
let long = "x".repeat(10_000);
let response = ErrorResponse::new(StatusCode::IM_A_TEAPOT, HeaderMap::new(), &long);
let Decoded::IpBanned { retry_after, body } = decode(response.into()) else {
panic!("a 418 is a ban");
};
assert_eq!(retry_after, None);
assert_eq!(body, long);
}
}