push-messaging 0.1.1

Push Messaging package
Documentation
use crate::client::{client::Client, error, message};
use reqwest::{Response, header::{CONTENT_TYPE, CONTENT_LENGTH, AUTHORIZATION, RETRY_AFTER}, StatusCode};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use tokio::time::sleep;

use super::{
    error::{ErrorReason, FcmError, RetryAfter},
    message::Message,
};

#[skip_serializing_none]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct FcmResponse {
    pub message_id: Option<u64>,
    pub error: Option<ErrorReason>,
    pub multicast_id: Option<i64>,
    pub success: Option<u64>,
    pub failure: Option<u64>,
    pub canonical_ids: Option<u64>,
    pub results: Option<Vec<MessageResult>>,
}
#[skip_serializing_none]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Serialize)]
pub struct MessageResult {
    pub message_id: Option<String>,
    pub registration_id: Option<String>,
    pub error: Option<ErrorReason>,
}

#[derive(Debug, Clone)]
pub struct FcmClient {
    client: reqwest::Client,
    api_key: String,
}

impl FcmClient {
    pub fn new(api_key: String) -> Self {
        Self {
            client: reqwest::Client::new(),
            api_key,
        }
    }
    pub async fn send_message(&self, message: &Message) -> Result<FcmResponse, FcmError> {
       let response = self.make_request(&message).await;
        match response {
            Err(FcmError::ServerError(Some(e))) => {
                //make same request again after delay in e
                let delay = match e {
                    RetryAfter::Delay(delay) => delay,
                    RetryAfter::DateTime(date) => {
                        let now = chrono::Utc::now();
                        date.signed_duration_since(now)
                    }
                };
                sleep(delay.to_std().unwrap()).await;
                return self.make_request(&message).await;
            }
            Err(e) => Err(e),
            Ok(e) => Ok(e),
        }
    }

    async fn decode_response(response: Response) -> Result<FcmResponse, FcmError> {
        let response_status = response.status();

        let retry_after = response
            .headers()
            .get(RETRY_AFTER)
            .and_then(|ra| ra.to_str().ok())
            .and_then(|ra| ra.parse::<RetryAfter>().ok());

        match response_status {
            StatusCode::OK => {
                let fcm_response: FcmResponse = response.json().await.unwrap();

                match fcm_response.error {
                    Some(ErrorReason::Unavailable) => Err(FcmError::ServerError(retry_after)),
                    Some(ErrorReason::InternalServerError) => Err(FcmError::ServerError(retry_after)),
                    _ => Ok(fcm_response),
                }
            }
            StatusCode::UNAUTHORIZED => Err(FcmError::Unauthorized),
            StatusCode::BAD_REQUEST => Err(FcmError::InvalidMessage("Bad Request".to_string())),
            status if status.is_server_error() => Err(FcmError::ServerError(retry_after)),
            _ => Err(FcmError::InvalidMessage("Unknown Error".to_string())),
        }
    }
    pub async fn make_request(&self, message: &Message) -> Result<FcmResponse, FcmError> {
        let url = format!("https://fcm.googleapis.com/fcm/send");
        let response = self
            .client
            .post(&url)
            .header(CONTENT_TYPE, "application/json")
            .header(AUTHORIZATION, format!("key={}", self.api_key.clone()).as_bytes())
            .bearer_auth(&self.api_key)
            .json(&message)
            .send()
            .await;
       Self::decode_response(response?).await
    }
}

impl Client for FcmClient {
    async fn send_by_token(&self, token: impl Into<String>, message: message::Message) -> Result<(), error::ClientError> {
        let mut message: Message = message.into();
        message.to = Some(token.into());
        let result = self.send_message(&message).await;
        if let Err(e) = result {
            return Err(e.into());
        } else {
            let result = result.unwrap();
            if let Some(error) = result.error {
                return Err(error.into());
            }
            if result.success.unwrap_or_default() == 0{
                return Err(error::ClientError::InvalidMessage("No message sent".to_string()));
            }
        }

        Ok(())
    }
    async fn send_by_topic(&self, topic: impl Into<String>, message: message::Message) -> Result<(), error::ClientError>{
        let mut message: Message = message.into();
        message.to = Some(topic.into());
        let result = self.send_message(&message).await;
        if let Err(e) = result {
            return Err(e.into());
        } else {
            let result = result.unwrap();
            if let Some(error) = result.error {
                return Err(error.into());
            }
            if result.success.unwrap_or_default() == 0{
                return Err(error::ClientError::InvalidMessage("No message sent".to_string()));
            }
        }

        Ok(())
    }

}

#[cfg(test)]
mod tests {
    use crate::client::{client::Client, message::Message, notification::Notification};

    use super::FcmClient;
    #[tokio::test]
    async fn test_send_message(){
        let api_key = "AAAAeGlIzok:APA91bFYZmDhEWk06UABcQIjgV8Y1JQAZA2ZezBnrhE6HHw0rjmh8IB8fno9UTVyg8iBNnKWPSD9-E61NhYG7aS696x_zvkvv265-mlugoYMv9Jo0ofBdvVd4TJspkPJC3cztMRR6aCf";
        let client = FcmClient::new(api_key.to_string());
        let notification = Notification::new()
            .title("test title")
            .body(Some("test body".into()))
            .build();
        let message = Message::new()
            .notification(Some(notification))
            .build();
        let fcm_message = message.into();
        let response = client.send_message(&fcm_message).await;
        dbg!(&response);
        assert!(response.is_ok());

    }
}