use crate::{
crypto, queries, utils,
v1::{
bool_to_int, response_payload, skip_serializing_none, ItemKind, LocationColor, PlainResponsePayload, Uuid,
METADATA_VERSION,
},
FilenSettings,
};
use secstr::SecUtf8;
use serde::{Deserialize, Serialize};
use serde_json::json;
use snafu::{ResultExt, Snafu};
type Result<T, E = Error> = std::result::Result<T, E>;
const CURRENT_VERSIONS_PATH: &str = "/v1/currentVersions";
const DIR_COLOR_CHANGE_PATH: &str = "/v1/dir/color/change";
const ITEM_FAVORITE_PATH: &str = "/v1/item/favorite";
const SYNC_CLIENT_MESSAGE_PATH: &str = "/v1/sync/client/message";
const TRASH_EMPTY_PATH: &str = "/v1/trash/empty";
#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("Cannot serialize data struct to JSON: {}", source))]
CannotSerializeDataToJson { source: serde_json::Error },
#[snafu(display("{} query failed: {}", CURRENT_VERSIONS_PATH, source))]
CurrentVersionsQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", DIR_COLOR_CHANGE_PATH, source))]
DirColorChangeQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", ITEM_FAVORITE_PATH, source))]
ItemFavoriteQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", SYNC_CLIENT_MESSAGE_PATH, source))]
SyncClientMessageQueryFailed { source: queries::Error },
#[snafu(display("{} query failed: {}", TRASH_EMPTY_PATH, source))]
TrashEmptyQueryFailed { source: queries::Error },
}
#[skip_serializing_none]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct CurrentVersionsResponseData {
pub desktop: String,
pub mobile: String,
}
utils::display_from_json!(CurrentVersionsResponseData);
response_payload!(
CurrentVersionsResponsePayload<CurrentVersionsResponseData>
);
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct DirColorChangeRequestPayload<'dir_color_change> {
#[serde(rename = "apiKey")]
pub api_key: &'dir_color_change SecUtf8,
pub color: LocationColor,
pub uuid: Uuid,
}
utils::display_from_json_with_lifetime!('dir_color_change, DirColorChangeRequestPayload);
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ItemFavoriteRequestPayload<'item_favorite> {
#[serde(rename = "apiKey")]
pub api_key: &'item_favorite SecUtf8,
pub uuid: Uuid,
#[serde(rename = "type")]
pub item_type: ItemKind,
#[serde(deserialize_with = "bool_from_int", serialize_with = "bool_to_int")]
pub value: bool,
}
utils::display_from_json_with_lifetime!('item_favorite, ItemFavoriteRequestPayload);
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct SyncClientMessageRequestPayload<'sync_client_message> {
#[serde(rename = "apiKey")]
pub api_key: &'sync_client_message SecUtf8,
pub args: String,
}
utils::display_from_json_with_lifetime!('sync_client_message, SyncClientMessageRequestPayload);
impl<'sync_client_message> SyncClientMessageRequestPayload<'sync_client_message> {
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn from_json(
api_key: &'sync_client_message SecUtf8,
json_value: &serde_json::Value,
last_master_key: &SecUtf8,
) -> Self {
let metadata =
crypto::encrypt_metadata_str(&json_value.to_string(), last_master_key, METADATA_VERSION).unwrap();
Self {
api_key,
args: metadata,
}
}
pub fn from_data<T: Serialize>(
api_key: &'sync_client_message SecUtf8,
data: T,
last_master_key: &SecUtf8,
) -> Result<Self> {
let json_value = serde_json::to_value(&data).context(CannotSerializeDataToJsonSnafu {})?;
Ok(Self::from_json(api_key, &json_value, last_master_key))
}
}
pub fn current_versions_request(filen_settings: &FilenSettings) -> Result<CurrentVersionsResponsePayload> {
queries::query_filen_api(CURRENT_VERSIONS_PATH, &json!(""), filen_settings)
.context(CurrentVersionsQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn current_versions_request_async(filen_settings: &FilenSettings) -> Result<CurrentVersionsResponsePayload> {
queries::query_filen_api_async(CURRENT_VERSIONS_PATH, &json!(""), filen_settings)
.await
.context(CurrentVersionsQueryFailedSnafu {})
}
pub fn dir_color_change_request(
payload: &DirColorChangeRequestPayload,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api(DIR_COLOR_CHANGE_PATH, payload, filen_settings).context(DirColorChangeQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn dir_color_change_request_async(
payload: &DirColorChangeRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api_async(DIR_COLOR_CHANGE_PATH, payload, filen_settings)
.await
.context(DirColorChangeQueryFailedSnafu {})
}
pub fn item_favorite_request(
payload: &ItemFavoriteRequestPayload,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api(ITEM_FAVORITE_PATH, payload, filen_settings).context(ItemFavoriteQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn item_favorite_request_async(
payload: &ItemFavoriteRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api_async(ITEM_FAVORITE_PATH, payload, filen_settings)
.await
.context(ItemFavoriteQueryFailedSnafu {})
}
pub fn sync_client_message_request(
payload: &SyncClientMessageRequestPayload,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api(SYNC_CLIENT_MESSAGE_PATH, payload, filen_settings)
.context(SyncClientMessageQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn sync_client_message_request_async(
payload: &SyncClientMessageRequestPayload<'_>,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api_async(SYNC_CLIENT_MESSAGE_PATH, payload, filen_settings)
.await
.context(SyncClientMessageQueryFailedSnafu {})
}
pub fn trash_empty_request(api_key: &SecUtf8, filen_settings: &FilenSettings) -> Result<PlainResponsePayload> {
queries::query_filen_api(TRASH_EMPTY_PATH, &utils::api_key_json(api_key), filen_settings)
.context(TrashEmptyQueryFailedSnafu {})
}
#[cfg(feature = "async")]
pub async fn trash_empty_request_async(
api_key: &SecUtf8,
filen_settings: &FilenSettings,
) -> Result<PlainResponsePayload> {
queries::query_filen_api_async(TRASH_EMPTY_PATH, &utils::api_key_json(api_key), filen_settings)
.await
.context(TrashEmptyQueryFailedSnafu {})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::validate_contract;
#[cfg(feature = "async")]
use crate::test_utils::validate_contract_async;
#[test]
fn current_versions_request_should_have_proper_contract() {
validate_contract(
CURRENT_VERSIONS_PATH,
json!(""),
"tests/resources/responses/current_versions.json",
|_, filen_settings| current_versions_request(&filen_settings),
);
}
#[cfg(feature = "async")]
#[tokio::test]
async fn user_dirs_request_async_should_have_proper_contract() {
validate_contract_async(
CURRENT_VERSIONS_PATH,
json!(""),
"tests/resources/responses/current_versions.json",
|_, filen_settings| async move { current_versions_request_async(&filen_settings).await },
)
.await;
}
}