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