web_push/clients/
request_builder.rsuse http::header::{CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE};
use http::{Request, StatusCode};
use crate::{error::WebPushError, message::WebPushMessage};
#[derive(Deserialize, Serialize, Debug, PartialEq)]
struct ErrorInfo {
code: u16,
errno: u16,
error: String,
message: String,
}
pub fn build_request<T>(message: WebPushMessage) -> Request<T>
where
T: From<Vec<u8>> + From<&'static str>, {
let mut builder = Request::builder()
.method("POST")
.uri(message.endpoint)
.header("TTL", format!("{}", message.ttl).as_bytes());
if let Some(urgency) = message.urgency {
builder = builder.header("Urgency", urgency.to_string());
}
if let Some(payload) = message.payload {
builder = builder
.header(CONTENT_ENCODING, payload.content_encoding.to_str())
.header(CONTENT_LENGTH, format!("{}", payload.content.len() as u64).as_bytes())
.header(CONTENT_TYPE, "application/octet-stream");
for (k, v) in payload.crypto_headers.into_iter() {
let v: &str = v.as_ref();
builder = builder.header(k, v);
}
builder.body(payload.content.into()).unwrap()
} else {
builder.body("".into()).unwrap()
}
}
pub fn parse_response(response_status: StatusCode, body: Vec<u8>) -> Result<(), WebPushError> {
match response_status {
status if status.is_success() => Ok(()),
status if status.is_server_error() => Err(WebPushError::ServerError(None)),
StatusCode::UNAUTHORIZED => Err(WebPushError::Unauthorized),
StatusCode::GONE => Err(WebPushError::EndpointNotValid),
StatusCode::NOT_FOUND => Err(WebPushError::EndpointNotFound),
StatusCode::PAYLOAD_TOO_LARGE => Err(WebPushError::PayloadTooLarge),
StatusCode::BAD_REQUEST => match String::from_utf8(body) {
Err(_) => Err(WebPushError::BadRequest(None)),
Ok(body_str) => match serde_json::from_str::<ErrorInfo>(&body_str) {
Ok(error_info) => Err(WebPushError::BadRequest(Some(error_info.error))),
Err(_) if body_str.is_empty() => Err(WebPushError::BadRequest(None)),
Err(_) => Err(WebPushError::BadRequest(None)),
},
},
e => Err(WebPushError::Other(format!("{:?}", e))),
}
}
#[cfg(test)]
mod tests {
use http::Uri;
use crate::clients::request_builder::*;
use crate::error::WebPushError;
use crate::http_ece::ContentEncoding;
use crate::message::WebPushMessageBuilder;
use crate::Urgency;
#[test]
fn builds_a_correct_request_with_empty_payload() {
let sub = serde_json::json!({"endpoint":"https://fcm.googleapis.com/fcm/send/eKClHsXFm9E:APA91bH2x3gNOMv4dF1lQfCgIfOet8EngqKCAUS5DncLOd5hzfSUxcjigIjw9ws-bqa-KmohqiTOcgepAIVO03N39dQfkEkopubML_m3fyvF03pV9_JCB7SxpUjcFmBSVhCaWS6m8l7x",
"expirationTime":null,
"keys":{"p256dh":
"BGa4N1PI79lboMR_YrwCiCsgp35DRvedt7opHcf0yM3iOBTSoQYqQLwWxAfRKE6tsDnReWmhsImkhDF_DBdkNSU",
"auth":"EvcWjEgzr4rbvhfi3yds0A"}
});
let info = serde_json::from_value(sub).unwrap();
let mut builder = WebPushMessageBuilder::new(&info).unwrap();
builder.set_ttl(420);
builder.set_urgency(Urgency::VeryLow);
let request = build_request::<isahc::Body>(builder.build().unwrap());
let ttl = request.headers().get("TTL").unwrap().to_str().unwrap();
let urgency = request.headers().get("Urgency").unwrap().to_str().unwrap();
let expected_uri: Uri = "fcm.googleapis.com".parse().unwrap();
assert_eq!("420", ttl);
assert_eq!("very-low", urgency);
assert_eq!(expected_uri.host(), request.uri().host());
}
#[test]
fn builds_a_correct_request_with_payload() {
let sub = serde_json::json!({"endpoint":"https://fcm.googleapis.com/fcm/send/eKClHsXFm9E:APA91bH2x3gNOMv4dF1lQfCgIfOet8EngqKCAUS5DncLOd5hzfSUxcjigIjw9ws-bqa-KmohqiTOcgepAIVO03N39dQfkEkopubML_m3fyvF03pV9_JCB7SxpUjcFmBSVhCaWS6m8l7x",
"expirationTime":null,
"keys":{"p256dh":
"BGa4N1PI79lboMR_YrwCiCsgp35DRvedt7opHcf0yM3iOBTSoQYqQLwWxAfRKE6tsDnReWmhsImkhDF_DBdkNSU",
"auth":"EvcWjEgzr4rbvhfi3yds0A"}
});
let info = serde_json::from_value(sub).unwrap();
let mut builder = WebPushMessageBuilder::new(&info).unwrap();
builder.set_payload(ContentEncoding::Aes128Gcm, "test".as_bytes());
let request = build_request::<isahc::Body>(builder.build().unwrap());
let encoding = request.headers().get("Content-Encoding").unwrap().to_str().unwrap();
let length = request.headers().get("Content-Length").unwrap();
let expected_uri: Uri = "fcm.googleapis.com".parse().unwrap();
assert_eq!("230", length);
assert_eq!("aes128gcm", encoding);
assert_eq!(expected_uri.host(), request.uri().host());
}
#[test]
fn parses_a_successful_response_correctly() {
assert_eq!(Ok(()), parse_response(StatusCode::OK, vec![]))
}
#[test]
fn parses_an_unauthorized_response_correctly() {
assert_eq!(
Err(WebPushError::Unauthorized),
parse_response(StatusCode::UNAUTHORIZED, vec![])
)
}
#[test]
fn parses_a_gone_response_correctly() {
assert_eq!(
Err(WebPushError::EndpointNotValid),
parse_response(StatusCode::GONE, vec![])
)
}
#[test]
fn parses_a_not_found_response_correctly() {
assert_eq!(
Err(WebPushError::EndpointNotFound),
parse_response(StatusCode::NOT_FOUND, vec![])
)
}
#[test]
fn parses_a_payload_too_large_response_correctly() {
assert_eq!(
Err(WebPushError::PayloadTooLarge),
parse_response(StatusCode::PAYLOAD_TOO_LARGE, vec![])
)
}
#[test]
fn parses_a_server_error_response_correctly() {
assert_eq!(
Err(WebPushError::ServerError(None)),
parse_response(StatusCode::INTERNAL_SERVER_ERROR, vec![])
)
}
#[test]
fn parses_a_bad_request_response_with_no_body_correctly() {
assert_eq!(
Err(WebPushError::BadRequest(None)),
parse_response(StatusCode::BAD_REQUEST, vec![])
)
}
#[test]
fn parses_a_bad_request_response_with_body_correctly() {
let json = r#"
{
"code": 404,
"errno": 103,
"error": "FooBar",
"message": "No message found"
}
"#;
assert_eq!(
Err(WebPushError::BadRequest(Some(String::from("FooBar")))),
parse_response(StatusCode::BAD_REQUEST, json.as_bytes().to_vec())
)
}
}