1use nextcloud_ocs::{OcsEnvelope, OcsStatus};
4use nextcloud_sdk_core::{
5 ApiPath, AuthProvider, DecodeError, Error, Headers, HttpMethod, Request, Response, StatusCode,
6 StatusError, Transport,
7};
8use serde::Deserialize;
9
10use crate::notification::Notification;
11
12#[cfg(test)]
13mod tests;
14
15pub const NOTIFICATIONS_API_PATH: &str = "apps/notifications/api/v2/notifications";
19
20const OCS_REQUEST_HEADER: &str = "OCS-APIRequest";
22const ACCEPT_HEADER: &str = "Accept";
24const ACCEPT_JSON: &str = "application/json";
25
26#[derive(Clone, Copy, Debug)]
37pub struct NotificationClient<'a, T: Transport, A: AuthProvider> {
38 transport: &'a T,
39 auth: &'a A,
40}
41
42impl<'a, T: Transport, A: AuthProvider> NotificationClient<'a, T, A> {
43 pub const fn new(transport: &'a T, auth: &'a A) -> Self {
45 Self { transport, auth }
46 }
47
48 pub const fn transport(&self) -> &'a T {
50 self.transport
51 }
52
53 pub const fn auth(&self) -> &'a A {
55 self.auth
56 }
57
58 pub async fn list(&self) -> Result<Vec<Notification>, Error> {
67 let api_path = ApiPath::ocs(NOTIFICATIONS_API_PATH)?;
68 let headers = self.authenticated_headers();
69 let request = Request::get(api_path.clone()).with_headers(headers);
70
71 let response = self.transport.execute(request).await?;
72 self.decode_list(api_path, response)
73 }
74
75 pub async fn get(&self, notification_id: u64) -> Result<Notification, Error> {
81 let api_path = ApiPath::ocs(format!("{NOTIFICATIONS_API_PATH}/{notification_id}"))?;
82 let headers = self.authenticated_headers();
83 let request = Request::get(api_path.clone()).with_headers(headers);
84
85 let response = self.transport.execute(request).await?;
86 self.decode_single(api_path, response)
87 }
88
89 pub async fn delete(&self, notification_id: u64) -> Result<(), Error> {
95 let api_path = ApiPath::ocs(format!("{NOTIFICATIONS_API_PATH}/{notification_id}"))?;
96 let headers = self.authenticated_headers();
97 let request = Request::new(HttpMethod::Delete, api_path.clone()).with_headers(headers);
98
99 let response = self.transport.execute(request).await?;
100 self.decode_empty(HttpMethod::Delete, api_path, response)
101 }
102
103 pub async fn delete_all(&self) -> Result<(), Error> {
109 let api_path = ApiPath::ocs(NOTIFICATIONS_API_PATH)?;
110 let headers = self.authenticated_headers();
111 let request = Request::new(HttpMethod::Delete, api_path.clone()).with_headers(headers);
112
113 let response = self.transport.execute(request).await?;
114 self.decode_empty(HttpMethod::Delete, api_path, response)
115 }
116
117 fn authenticated_headers(&self) -> Headers {
118 let mut headers = Headers::new();
119 headers.set(OCS_REQUEST_HEADER, "true");
120 headers.set(ACCEPT_HEADER, ACCEPT_JSON);
121 self.auth.authenticate(&mut headers);
122 headers
123 }
124
125 fn decode_list(&self, path: ApiPath, response: Response) -> Result<Vec<Notification>, Error> {
126 if response.status() == StatusCode::new(204) {
127 return Ok(Vec::new());
128 }
129
130 if !response.status().is_success() {
131 return Err(Error::Status(StatusError::new(
132 HttpMethod::Get.as_str(),
133 path,
134 response.status(),
135 response.body(),
136 )));
137 }
138
139 if response.body().is_empty() {
140 return Ok(Vec::new());
141 }
142
143 let envelope: OcsEnvelope<Option<Vec<Notification>>> =
144 serde_json::from_slice(response.body())
145 .map_err(|cause| {
146 DecodeError::new(
147 std::any::type_name::<OcsEnvelope<Vec<Notification>>>(),
148 cause,
149 )
150 })
151 .map_err(Error::Decode)?;
152
153 if envelope.meta().verdict() == Some(OcsStatus::Failure) {
154 return Err(Error::Status(StatusError::new(
155 HttpMethod::Get.as_str(),
156 path,
157 response.status(),
158 response.body(),
159 )));
160 }
161
162 Ok(envelope.into_data().unwrap_or_default())
163 }
164
165 fn decode_single(&self, path: ApiPath, response: Response) -> Result<Notification, Error> {
166 if !response.status().is_success() {
167 return Err(Error::Status(StatusError::new(
168 HttpMethod::Get.as_str(),
169 path,
170 response.status(),
171 response.body(),
172 )));
173 }
174
175 let envelope: OcsEnvelope<NotificationOrList> = serde_json::from_slice(response.body())
176 .map_err(|cause| {
177 DecodeError::new(std::any::type_name::<OcsEnvelope<Notification>>(), cause)
178 })
179 .map_err(Error::Decode)?;
180
181 if envelope.meta().verdict() == Some(OcsStatus::Failure) {
182 return Err(Error::Status(StatusError::new(
183 HttpMethod::Get.as_str(),
184 path,
185 response.status(),
186 response.body(),
187 )));
188 }
189
190 envelope.into_data().into_notification()
191 }
192
193 fn decode_empty(
194 &self,
195 method: HttpMethod,
196 path: ApiPath,
197 response: Response,
198 ) -> Result<(), Error> {
199 if !response.status().is_success() {
200 return Err(Error::Status(StatusError::new(
201 method.as_str(),
202 path,
203 response.status(),
204 response.body(),
205 )));
206 }
207
208 if response.body().is_empty() {
209 return Ok(());
210 }
211
212 let envelope: OcsEnvelope<serde_json::Value> = serde_json::from_slice(response.body())
213 .map_err(|cause| {
214 DecodeError::new(
215 std::any::type_name::<OcsEnvelope<serde_json::Value>>(),
216 cause,
217 )
218 })
219 .map_err(Error::Decode)?;
220
221 if envelope.meta().verdict() == Some(OcsStatus::Failure) {
222 return Err(Error::Status(StatusError::new(
223 method.as_str(),
224 path,
225 response.status(),
226 response.body(),
227 )));
228 }
229
230 Ok(())
231 }
232}
233
234#[derive(Deserialize)]
237#[serde(untagged)]
238enum NotificationOrList {
239 Single(Notification),
240 List(Vec<Notification>),
241}
242
243impl NotificationOrList {
244 fn into_notification(self) -> Result<Notification, Error> {
245 match self {
246 Self::Single(notification) => Ok(notification),
247 Self::List(mut list) => list.pop().ok_or_else(|| {
248 Error::Decode(DecodeError::new(
249 std::any::type_name::<Notification>(),
250 std::io::Error::new(
251 std::io::ErrorKind::NotFound,
252 "notification response list was empty",
253 ),
254 ))
255 }),
256 }
257 }
258}