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 DocTarget {
Document(Box<DriveFile>),
Shortcut {
name: String,
},
NotADocument {
name: String,
mime_type: String,
},
}
impl DocTarget {
#[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_DOC_MIME_TYPE {
return Self::Document(Box::new(file));
}
Self::NotADocument {
name: file.name,
mime_type: file.mime_type,
}
}
#[must_use]
pub fn document(&self) -> Option<&DriveFile> {
match self {
Self::Document(file) => Some(file),
_ => None,
}
}
#[must_use]
pub fn refusal_message(&self, verb: &str) -> Option<String> {
match self {
Self::Document(_) => None,
Self::Shortcut { name } => Some(format!(
"Refused: '{name}' is a shortcut; `drive docs {verb}` doesn't follow shortcuts — \
resolve the target document's id and use that instead"
)),
Self::NotADocument { name, mime_type } => {
let base = format!(
"Refused: '{name}' is not a Google Doc (mimeType: {mime_type}); \
`drive docs {verb}` only works on Google Docs"
);
match mime_type.as_str() {
GOOGLE_SHEET_MIME_TYPE => {
Some(format!("{base} — try `omni-dev drive sheets read` instead"))
}
GOOGLE_SLIDES_MIME_TYPE => {
Some(format!("{base} — try `omni-dev drive slides read` instead"))
}
_ => Some(base),
}
}
}
}
}
pub async fn classify(api: &FilesApi<'_>, file_id: &str) -> Result<DocTarget> {
Ok(DocTarget::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_google_doc() {
let target = DocTarget::of(file("Design Doc", GOOGLE_DOC_MIME_TYPE));
assert!(target.refusal_message("read").is_none());
assert_eq!(target.document().unwrap().name, "Design Doc");
}
#[test]
fn classify_checks_shortcut_before_the_mime_check() {
let target = DocTarget::of(file("Design Doc (link)", GOOGLE_SHORTCUT_MIME_TYPE));
assert!(matches!(target, DocTarget::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 Doc"), "{message}");
}
#[test]
fn refusal_for_a_spreadsheet_points_at_drive_sheets_read() {
let message = DocTarget::of(file("Q3", GOOGLE_SHEET_MIME_TYPE))
.refusal_message("read")
.unwrap();
assert!(message.contains("not a Google Doc"), "{message}");
assert!(message.contains("drive sheets read"), "{message}");
}
#[test]
fn refusal_for_a_presentation_points_at_drive_slides() {
let message = DocTarget::of(file("Deck", GOOGLE_SLIDES_MIME_TYPE))
.refusal_message("read")
.unwrap();
assert!(message.contains("drive slides read"), "{message}");
}
#[test]
fn refusal_for_a_plain_file_names_its_mime_type_without_a_hint() {
let message = DocTarget::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 = DocTarget::of(file("Q3", GOOGLE_SHEET_MIME_TYPE));
assert!(target
.refusal_message("info")
.unwrap()
.contains("drive docs info"));
assert!(target
.refusal_message("read")
.unwrap()
.contains("drive docs read"));
}
}