Skip to content

Commit

Permalink
Implement chunk (#138)
Browse files Browse the repository at this point in the history
* Implement chunk

Fixes #91

Implement chunking functionality for push notifications.

* Add `chunk_push_notifications` method to `Expo` struct in `src/expo_client.rs` to split messages into chunks of 100.
* Update `send_push_notifications` method in `src/expo_client.rs` to use `chunk_push_notifications` method.
* Add test cases for `chunk_push_notifications` method in `tests/expo_client.rs`.
* Replace `unwrap` with `anyhow` for error handling in `src/error.rs` and `src/object/expo_push_message.rs`.

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/katayama8000/expo-push-notification-client-rust/issues/91?shareId=XXXX-XXXX-XXXX-XXXX).

* Refactor error handling and improve chunk_push_notifications method

* Remove unwrap()

* Refactor chunk_push_notifications to return SendPushNotificationsRequest

* make chunk_push_notifications private

* Add unit test for chunk_push_notifications method

* Optimize chunking of messages in chunk_push_notifications method

* Add comment to indicate private methods in Expo implementation

* Bump version to 0.6.0 in Cargo.toml
  • Loading branch information
katayama8000 authored Jan 2, 2025
1 parent 3461923 commit 5cf1ec8
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "expo_push_notification_client"
version = "0.5.2"
version = "0.6.0"
edition = "2021"
readme = "README.md"
authors = ["katayama8000 <https://github.com/katayama8000>"]
Expand Down
55 changes: 50 additions & 5 deletions src/expo_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use tokio::io::AsyncWriteExt;

use crate::{
error::CustomError,
object::{ExpoPushReceipt, ExpoPushTicket, TryIntoSendPushNotificationsRequest},
object::{
ExpoPushMessage, ExpoPushReceipt, ExpoPushTicket, SendPushNotificationsRequest,
TryIntoSendPushNotificationsRequest,
},
ExpoPushReceiptId,
};

Expand Down Expand Up @@ -109,10 +112,15 @@ impl Expo {
R: TryIntoSendPushNotificationsRequest,
{
let request = request.try_into_send_push_notifications_request()?;
let response: SendPushNotificationSuccessfulResponse = self
.send_request(Method::POST, "/--/api/v2/push/send", request)
.await?;
Ok(response.data)
let chunks = self.chunk_push_notifications(request.messages());
let mut tickets = Vec::new();
for chunk in chunks {
let response: SendPushNotificationSuccessfulResponse = self
.send_request(Method::POST, "/--/api/v2/push/send", chunk)
.await?;
tickets.extend(response.data);
}
Ok(tickets)
}

/// Get push notification receipts
Expand Down Expand Up @@ -185,6 +193,8 @@ impl Expo {
Ok(response.data)
}

// private methods

async fn gzip(src: &[u8]) -> std::io::Result<Vec<u8>> {
let mut encoder = GzipEncoder::new(vec![]);
encoder.write_all(src).await?;
Expand Down Expand Up @@ -252,6 +262,16 @@ impl Expo {
Err(err) => Err(CustomError::ServerErr(format!("Request failed: {}", err))),
}
}

fn chunk_push_notifications(
&self,
messages: Vec<ExpoPushMessage>,
) -> Vec<SendPushNotificationsRequest> {
messages
.chunks(100)
.map(|chunk| SendPushNotificationsRequest::from(chunk.to_vec()))
.collect()
}
}

#[cfg(test)]
Expand Down Expand Up @@ -289,6 +309,31 @@ mod tests {
}
}

#[test]
fn test_chunk_expo_push_request() -> anyhow::Result<()> {
let expo = Expo::new(ExpoClientOptions::default());

let test_cases = vec![(1, 1), (100, 1), (101, 2), (250, 3)];

for (message_count, expected_chunks) in test_cases {
let messages = (0..message_count)
.map(|_| {
ExpoPushMessage::builder(["ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]"]).build()
})
.collect::<Result<Vec<_>, _>>()?;

let chunks = expo.chunk_push_notifications(messages);

assert_eq!(
chunks.len(),
expected_chunks,
"Failed for {} messages",
message_count
);
}

Ok(())
}
#[tokio::test]
async fn test_get_push_notification_receipts() -> anyhow::Result<()> {
let mut server = mockito::Server::new_async().await;
Expand Down
2 changes: 1 addition & 1 deletion src/object/expo_push_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::error::ValidationError;

// <https://docs.expo.dev/push-notifications/sending-notifications/#message-request-format>
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ExpoPushMessage {
to: Vec<String>,
Expand Down
14 changes: 13 additions & 1 deletion src/object/send_push_notifications_request.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
use crate::{CustomError, ExpoPushMessage};

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct SendPushNotificationsRequest(Vec<ExpoPushMessage>);

impl From<Vec<ExpoPushMessage>> for SendPushNotificationsRequest {
fn from(messages: Vec<ExpoPushMessage>) -> Self {
Self(messages)
}
}

impl SendPushNotificationsRequest {
pub fn messages(&self) -> Vec<ExpoPushMessage> {
self.0.clone()
}
}

impl SendPushNotificationsRequest {
fn new(messages: Vec<ExpoPushMessage>) -> Result<Self, CustomError> {
if messages.is_empty() {
Expand Down

0 comments on commit 5cf1ec8

Please sign in to comment.