use nextcloud_ocs::{OcsEnvelope, OcsStatus};
use nextcloud_sdk_core::{
ApiPath, AuthProvider, DecodeError, Error, Headers, HttpMethod, Request, Response, StatusCode,
StatusError, Transport,
};
use serde::Deserialize;
use crate::notification::Notification;
#[cfg(test)]
mod tests;
pub const NOTIFICATIONS_API_PATH: &str = "apps/notifications/api/v2/notifications";
const OCS_REQUEST_HEADER: &str = "OCS-APIRequest";
const ACCEPT_HEADER: &str = "Accept";
const ACCEPT_JSON: &str = "application/json";
#[derive(Clone, Copy, Debug)]
pub struct NotificationClient<'a, T: Transport, A: AuthProvider> {
transport: &'a T,
auth: &'a A,
}
impl<'a, T: Transport, A: AuthProvider> NotificationClient<'a, T, A> {
pub const fn new(transport: &'a T, auth: &'a A) -> Self {
Self { transport, auth }
}
pub const fn transport(&self) -> &'a T {
self.transport
}
pub const fn auth(&self) -> &'a A {
self.auth
}
pub async fn list(&self) -> Result<Vec<Notification>, Error> {
let api_path = ApiPath::ocs(NOTIFICATIONS_API_PATH)?;
let headers = self.authenticated_headers();
let request = Request::get(api_path.clone()).with_headers(headers);
let response = self.transport.execute(request).await?;
self.decode_list(api_path, response)
}
pub async fn get(&self, notification_id: u64) -> Result<Notification, Error> {
let api_path = ApiPath::ocs(format!("{NOTIFICATIONS_API_PATH}/{notification_id}"))?;
let headers = self.authenticated_headers();
let request = Request::get(api_path.clone()).with_headers(headers);
let response = self.transport.execute(request).await?;
self.decode_single(api_path, response)
}
pub async fn delete(&self, notification_id: u64) -> Result<(), Error> {
let api_path = ApiPath::ocs(format!("{NOTIFICATIONS_API_PATH}/{notification_id}"))?;
let headers = self.authenticated_headers();
let request = Request::new(HttpMethod::Delete, api_path.clone()).with_headers(headers);
let response = self.transport.execute(request).await?;
self.decode_empty(HttpMethod::Delete, api_path, response)
}
pub async fn delete_all(&self) -> Result<(), Error> {
let api_path = ApiPath::ocs(NOTIFICATIONS_API_PATH)?;
let headers = self.authenticated_headers();
let request = Request::new(HttpMethod::Delete, api_path.clone()).with_headers(headers);
let response = self.transport.execute(request).await?;
self.decode_empty(HttpMethod::Delete, api_path, response)
}
fn authenticated_headers(&self) -> Headers {
let mut headers = Headers::new();
headers.set(OCS_REQUEST_HEADER, "true");
headers.set(ACCEPT_HEADER, ACCEPT_JSON);
self.auth.authenticate(&mut headers);
headers
}
fn decode_list(&self, path: ApiPath, response: Response) -> Result<Vec<Notification>, Error> {
if response.status() == StatusCode::new(204) {
return Ok(Vec::new());
}
if !response.status().is_success() {
return Err(Error::Status(StatusError::new(
HttpMethod::Get.as_str(),
path,
response.status(),
response.body(),
)));
}
if response.body().is_empty() {
return Ok(Vec::new());
}
let envelope: OcsEnvelope<Option<Vec<Notification>>> =
serde_json::from_slice(response.body())
.map_err(|cause| {
DecodeError::new(
std::any::type_name::<OcsEnvelope<Vec<Notification>>>(),
cause,
)
})
.map_err(Error::Decode)?;
if envelope.meta().verdict() == Some(OcsStatus::Failure) {
return Err(Error::Status(StatusError::new(
HttpMethod::Get.as_str(),
path,
response.status(),
response.body(),
)));
}
Ok(envelope.into_data().unwrap_or_default())
}
fn decode_single(&self, path: ApiPath, response: Response) -> Result<Notification, Error> {
if !response.status().is_success() {
return Err(Error::Status(StatusError::new(
HttpMethod::Get.as_str(),
path,
response.status(),
response.body(),
)));
}
let envelope: OcsEnvelope<NotificationOrList> = serde_json::from_slice(response.body())
.map_err(|cause| {
DecodeError::new(std::any::type_name::<OcsEnvelope<Notification>>(), cause)
})
.map_err(Error::Decode)?;
if envelope.meta().verdict() == Some(OcsStatus::Failure) {
return Err(Error::Status(StatusError::new(
HttpMethod::Get.as_str(),
path,
response.status(),
response.body(),
)));
}
envelope.into_data().into_notification()
}
fn decode_empty(
&self,
method: HttpMethod,
path: ApiPath,
response: Response,
) -> Result<(), Error> {
if !response.status().is_success() {
return Err(Error::Status(StatusError::new(
method.as_str(),
path,
response.status(),
response.body(),
)));
}
if response.body().is_empty() {
return Ok(());
}
let envelope: OcsEnvelope<serde_json::Value> = serde_json::from_slice(response.body())
.map_err(|cause| {
DecodeError::new(
std::any::type_name::<OcsEnvelope<serde_json::Value>>(),
cause,
)
})
.map_err(Error::Decode)?;
if envelope.meta().verdict() == Some(OcsStatus::Failure) {
return Err(Error::Status(StatusError::new(
method.as_str(),
path,
response.status(),
response.body(),
)));
}
Ok(())
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum NotificationOrList {
Single(Notification),
List(Vec<Notification>),
}
impl NotificationOrList {
fn into_notification(self) -> Result<Notification, Error> {
match self {
Self::Single(notification) => Ok(notification),
Self::List(mut list) => list.pop().ok_or_else(|| {
Error::Decode(DecodeError::new(
std::any::type_name::<Notification>(),
std::io::Error::new(
std::io::ErrorKind::NotFound,
"notification response list was empty",
),
))
}),
}
}
}