use anyhow::Result;
use crate::drive::files_api::FilesApi;
use crate::drive::types::{
DriveFile, GOOGLE_DOC_MIME_TYPE, GOOGLE_SHEET_MIME_TYPE, GOOGLE_SHORTCUT_MIME_TYPE,
GOOGLE_SLIDES_MIME_TYPE,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SlidesTarget {
Presentation(Box<DriveFile>),
Shortcut {
name: String,
},
NotAPresentation {
name: String,
mime_type: String,
},
}
impl SlidesTarget {
#[must_use]
pub fn of(file: DriveFile) -> Self {
if file.mime_type == GOOGLE_SHORTCUT_MIME_TYPE {
return Self::Shortcut { name: file.name };
}
if file.mime_type == GOOGLE_SLIDES_MIME_TYPE {
return Self::Presentation(Box::new(file));
}
Self::NotAPresentation {
name: file.name,
mime_type: file.mime_type,
}
}
#[must_use]
pub fn presentation(&self) -> Option<&DriveFile> {
match self {
Self::Presentation(file) => Some(file),
_ => None,
}
}
#[must_use]
pub fn refusal_message(&self, verb: &str) -> Option<String> {
match self {
Self::Presentation(_) => None,
Self::Shortcut { name } => Some(format!(
"Refused: '{name}' is a shortcut; `drive slides {verb}` doesn't follow shortcuts — \
resolve the target presentation's id and use that instead"
)),
Self::NotAPresentation { name, mime_type } => {
let base = format!(
"Refused: '{name}' is not a Google Slides presentation (mimeType: {mime_type}); \
`drive slides {verb}` only works on Google Slides"
);
match mime_type.as_str() {
GOOGLE_SHEET_MIME_TYPE => {
Some(format!("{base} — try `omni-dev drive sheets read` instead"))
}
GOOGLE_DOC_MIME_TYPE => {
Some(format!("{base} — try `omni-dev drive docs read` instead"))
}
_ => Some(base),
}
}
}
}
}
pub async fn classify(api: &FilesApi<'_>, file_id: &str) -> Result<SlidesTarget> {
Ok(SlidesTarget::of(api.get_metadata(file_id).await?))
}
pub async fn explain_failure(
api: &FilesApi<'_>,
file_id: &str,
verb: &str,
err: anyhow::Error,
) -> anyhow::Error {
match classify(api, file_id).await {
Ok(target) => target
.refusal_message(verb)
.map_or(err, |message| anyhow::anyhow!(message)),
Err(_) => err,
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn file(name: &str, mime_type: &str) -> DriveFile {
DriveFile {
id: "d1".to_string(),
name: name.to_string(),
mime_type: mime_type.to_string(),
..DriveFile::default()
}
}
#[test]
fn classify_recognises_a_presentation() {
let target = SlidesTarget::of(file("Deck", GOOGLE_SLIDES_MIME_TYPE));
assert!(target.refusal_message("read").is_none());
assert_eq!(target.presentation().unwrap().name, "Deck");
}
#[test]
fn classify_checks_shortcut_before_the_mime_check() {
let target = SlidesTarget::of(file("Deck (link)", GOOGLE_SHORTCUT_MIME_TYPE));
assert!(matches!(target, SlidesTarget::Shortcut { .. }));
let message = target.refusal_message("read").unwrap();
assert!(message.contains("is a shortcut"), "{message}");
assert!(message.contains("doesn't follow shortcuts"), "{message}");
assert!(
!message.contains("not a Google Slides presentation"),
"{message}"
);
}
#[test]
fn refusal_for_a_spreadsheet_points_at_drive_sheets_read() {
let message = SlidesTarget::of(file("Q3", GOOGLE_SHEET_MIME_TYPE))
.refusal_message("read")
.unwrap();
assert!(
message.contains("not a Google Slides presentation"),
"{message}"
);
assert!(message.contains("drive sheets read"), "{message}");
}
#[test]
fn refusal_for_a_doc_points_at_drive_docs() {
let message = SlidesTarget::of(file("Deck", GOOGLE_DOC_MIME_TYPE))
.refusal_message("read")
.unwrap();
assert!(message.contains("drive docs read"), "{message}");
}
#[test]
fn refusal_for_a_plain_file_names_its_mime_type_without_a_hint() {
let message = SlidesTarget::of(file("notes.txt", "text/plain"))
.refusal_message("read")
.unwrap();
assert!(message.contains("text/plain"), "{message}");
assert!(!message.contains("try `omni-dev"), "{message}");
}
#[test]
fn refusal_names_the_verb() {
let target = SlidesTarget::of(file("Q3", GOOGLE_SHEET_MIME_TYPE));
assert!(target
.refusal_message("info")
.unwrap()
.contains("drive slides info"));
assert!(target
.refusal_message("read")
.unwrap()
.contains("drive slides read"));
}
}