Skip to main content

doiget_cli/commands/
info.rs

1//! `doiget info <ref>` subcommand — read-only metadata inspection.
2//!
3//! Reads the metadata TOML for a given [`Ref`] from the [`FsStore`] and
4//! prints it on stdout. Network access is never required.
5//!
6//! Per [`docs/PUBLIC_API.md`](../../../../docs/PUBLIC_API.md) §2 the read goes
7//! through the [`Store::read`] trait method, so any future store backend
8//! (e.g. a SQLite index in Phase 2) is a drop-in replacement.
9
10use std::io::Write;
11
12use anyhow::{bail, Context, Result};
13
14use doiget_core::store::{FsStore, Store};
15use doiget_core::Ref;
16
17use super::resolve_store_root;
18
19/// Run the `info` subcommand against the configured store.
20///
21/// `input` is the user-supplied ref string (e.g. `"10.1234/example"`,
22/// `"arxiv:2401.12345"`, or any of the schemes accepted by [`Ref::parse`]).
23///
24/// On success, the entry's [`Metadata`](doiget_core::store::Metadata) is
25/// written to stdout as TOML. On a missing entry, the function returns an
26/// error so the CLI exits non-zero — the caller (a shell pipeline) can
27/// distinguish "entry not in store" from "entry was empty".
28pub fn run(input: String) -> Result<()> {
29    let ref_ = Ref::parse(&input).with_context(|| format!("invalid ref: {input}"))?;
30    let safekey = ref_.safekey();
31
32    let store_root = resolve_store_root()?;
33    let store = FsStore::new(store_root)?;
34
35    let metadata = store
36        .read(&safekey)
37        .with_context(|| format!("failed to read store entry for {input}"))?;
38
39    match metadata {
40        Some(m) => {
41            // Re-serialize to TOML for stdout. We use `toml::to_string_pretty`
42            // for human readability; this is NOT the §7-normalized form
43            // written to disk, but `info` is a presentation surface, not a
44            // round-tripper.
45            let s = toml::to_string_pretty(&m)
46                .context("failed to serialize metadata to TOML for stdout")?;
47            // Workspace lints deny `print_stdout` (the `print!`/`println!`
48            // macros) so JSON-RPC frames never collide with diagnostics.
49            // `writeln!` / `write!` against an explicit `stdout().lock()`
50            // is the sanctioned escape hatch — the caller chose stdout
51            // explicitly. See `docs/SECURITY.md` §3 / ADR-0001.
52            let stdout = std::io::stdout();
53            let mut out = stdout.lock();
54            write!(out, "{s}").context("failed to write metadata to stdout")?;
55            // toml::to_string_pretty already emits a trailing newline, but
56            // be defensive in case a future toml-rs revision changes that.
57            if !s.ends_with('\n') {
58                writeln!(out).context("failed to write trailing newline")?;
59            }
60            Ok(())
61        }
62        None => bail!("no entry for {input}"),
63    }
64}