1#![warn(missing_docs)]
2use reqwest::{
5 Client as HttpClient, IntoUrl, Response, Url,
6 header::{self, HeaderMap, HeaderValue},
7};
8
9pub use raw::{Operation, OperationResponse};
10
11use error::operation_errors::OperationResult;
12pub mod error;
13mod raw;
14
15pub const FIREBASE_NOTIFICATION_URL: &str = "https://fcm.googleapis.com/fcm/notification";
17
18#[derive(Debug, Clone)]
20pub struct FCMDeviceGroupClient {
21 url: Url,
22 client: HttpClient,
23}
24
25#[derive(Debug)]
27pub struct FCMDeviceGroup {
28 pub notification_key_name: String,
30 pub notification_key: String,
34}
35
36impl FCMDeviceGroupClient {
37 pub fn new(
39 sender_id: &str,
40 bearer_auth: &str,
41 ) -> Result<Self, error::FCMDeviceGroupClientCreationError> {
42 Self::with_url(FIREBASE_NOTIFICATION_URL, sender_id, bearer_auth)
43 }
44
45 pub fn with_url(
47 url: impl IntoUrl,
48 sender_id: &str,
49 bearer_auth: &str,
50 ) -> Result<Self, error::FCMDeviceGroupClientCreationError> {
51 let mut auth_header = header::HeaderValue::try_from(format!("Bearer {}", bearer_auth))?;
52 auth_header.set_sensitive(true);
53
54 let mut headers = HeaderMap::new();
55 headers.insert(header::AUTHORIZATION, auth_header);
56 headers.insert("project_id", header::HeaderValue::try_from(sender_id)?);
57 headers.insert(
58 "access_token_auth",
59 header::HeaderValue::from_static("true"),
60 );
61
62 Ok(Self {
63 url: url.into_url().unwrap(),
64 client: HttpClient::builder()
65 .default_headers(headers)
66 .connection_verbose(true)
67 .build()?,
68 })
69 }
70
71 pub fn with_client(client: HttpClient, url: impl IntoUrl) -> Self {
74 Self {
75 url: url.into_url().unwrap(),
76 client,
77 }
78 }
79
80 pub async fn apply(
82 &self,
83 operation: Operation,
84 ) -> Result<
85 OperationResponse,
86 error::FCMDeviceGroupsRequestError<error::FCMDeviceGroupsBadRequest>,
87 > {
88 let response = self.apply_raw(operation).await?;
89 error::FCMDeviceGroupsRequestError::json_response(response).await
90 }
91
92 pub async fn create_group(
94 &self,
95 notification_key_name: String,
96 registration_ids: Vec<String>,
97 ) -> OperationResult<FCMDeviceGroup, error::operation_errors::CreateGroupError> {
98 self.apply_operation(Operation::Create {
99 notification_key_name: notification_key_name.clone(),
100 registration_ids,
101 })
102 .await
103 }
104
105 pub async fn add_to_group(
107 &self,
108 group: FCMDeviceGroup,
109 registration_ids: Vec<String>,
110 ) -> OperationResult<FCMDeviceGroup, error::operation_errors::ChangeGroupMembersError> {
111 self.apply_operation(Operation::Add {
112 notification_key_name: Some(group.notification_key_name),
113 notification_key: group.notification_key,
114 registration_ids,
115 })
116 .await
117 }
118
119 pub async fn remove_from_group(
121 &self,
122 group: FCMDeviceGroup,
123 registration_ids: Vec<String>,
124 ) -> OperationResult<FCMDeviceGroup, error::operation_errors::ChangeGroupMembersError> {
125 self.apply_operation(Operation::Remove {
126 notification_key_name: Some(group.notification_key_name),
127 notification_key: group.notification_key,
128 registration_ids,
129 })
130 .await
131 }
132
133 pub async fn get_key(
135 &self,
136 notification_key_name: String,
137 ) -> OperationResult<FCMDeviceGroup, error::operation_errors::GetKeyError> {
138 let response = self
139 .client
140 .get(self.url.clone())
141 .query(&[("notification_key_name", notification_key_name.as_str())])
142 .header(
143 header::CONTENT_TYPE,
144 HeaderValue::from_static("application/json"),
145 )
146 .send()
147 .await?;
148 let response =
149 error::FCMDeviceGroupsRequestError::<error::operation_errors::GetKeyError>::json_response::<OperationResponse>(response)
150 .await?;
151 Ok(FCMDeviceGroup {
152 notification_key_name,
153 notification_key: response.notification_key,
154 })
155 }
156
157 async fn apply_raw(&self, operation: Operation) -> Result<Response, reqwest::Error> {
158 self.client
159 .post(self.url.clone())
160 .json(&operation)
161 .send()
162 .await
163 }
164
165 async fn apply_operation<E: error::FCMDeviceGroupError>(
166 &self,
167 operation: Operation,
168 ) -> OperationResult<FCMDeviceGroup, E> {
169 let key_name = match &operation {
170 Operation::Create {
171 notification_key_name,
172 ..
173 } => notification_key_name.to_owned(),
174 Operation::Add {
175 notification_key_name,
176 ..
177 } => notification_key_name
178 .as_ref()
179 .expect("Applying an operation should always have a key name")
180 .to_owned(),
181 Operation::Remove {
182 notification_key_name,
183 ..
184 } => notification_key_name
185 .as_ref()
186 .expect("Applying an operation should always have a key name")
187 .to_owned(),
188 };
189 let response = self.apply_raw(operation).await?;
190 let response =
191 error::FCMDeviceGroupsRequestError::<E>::json_response::<OperationResponse>(response)
192 .await?;
193 Ok(FCMDeviceGroup {
194 notification_key_name: key_name,
195 notification_key: response.notification_key,
196 })
197 }
198}