tftio-org-gdocs 0.1.1

Sync org-mode documents to Google Docs and pull reviewer comments back into org-mode
Documentation
//! Q3 — the `open` command: report the linked doc's browser URL for elisp to
//! `browse-url`.
//!
//! A pure, read-only lookup over the file's tool-owned header keywords (DI-3): it
//! reads `#+GDOC_ID:` (required) and `#+GDOC_URL:` (deriving the canonical edit
//! URL from the id when the keyword is absent), and never touches the body or the
//! network. A file with no linked doc surfaces the typed [`Error::NotLinked`]
//! rather than panicking (EI-1/EI-2).

use crate::envelope;
use crate::error::{Error, Result};
use crate::google::docs::DocumentRef;
use crate::orgfile;
use crate::sexp::Sexp;

/// The linked document a file points at.
#[derive(Debug, Clone)]
pub struct OpenOutcome {
    /// The linked document id (`#+GDOC_ID:`).
    pub document_id: String,
    /// The browser URL — the stored `#+GDOC_URL:`, or the canonical edit URL
    /// derived from the id.
    pub url: String,
}

/// Resolve the linked document URL from `content`'s header keywords.
///
/// # Errors
///
/// Returns [`Error::NotLinked`] when the file carries no `#+GDOC_ID:`.
pub fn open(content: &str) -> Result<OpenOutcome> {
    let document_id = orgfile::read_keyword(content, "GDOC_ID").ok_or(Error::NotLinked)?;
    let url = orgfile::read_keyword(content, "GDOC_URL")
        .unwrap_or_else(|| DocumentRef::from_id(document_id.clone()).url);
    Ok(OpenOutcome { document_id, url })
}

/// Build the success envelope for an `open` lookup (A5).
#[must_use]
pub fn envelope(outcome: &OpenOutcome) -> Sexp {
    envelope::ok(
        "open",
        vec![
            ("document-id", Sexp::string(outcome.document_id.clone())),
            ("url", Sexp::string(outcome.url.clone())),
        ],
    )
}

#[cfg(test)]
mod tests {
    use super::open;
    use crate::error::Error;

    #[test]
    fn open_prefers_the_stored_url() {
        let content =
            "#+GDOC_ID: DOC1\n#+GDOC_URL: https://docs.google.com/document/d/DOC1/edit\n* Intro\n";
        let outcome = open(content).expect("open succeeds");
        assert_eq!(outcome.document_id, "DOC1");
        assert_eq!(outcome.url, "https://docs.google.com/document/d/DOC1/edit");
    }

    #[test]
    fn open_derives_url_when_only_id_is_present() {
        let content = "#+GDOC_ID: ABC123\n* Intro\nbody\n";
        let outcome = open(content).expect("open succeeds");
        assert_eq!(
            outcome.url,
            "https://docs.google.com/document/d/ABC123/edit"
        );
    }

    #[test]
    fn open_without_a_linked_doc_is_not_linked_error() {
        let content = "* Intro\nNo link here.\n";
        assert!(matches!(open(content), Err(Error::NotLinked)));
    }
}