#![cfg(feature = "citation")]
use std::io::Write;
use anyhow::{Context, Result};
use doiget_core::citation_graph::{expand, GraphCaps, GraphError};
use doiget_core::sources::openalex::OpenalexSource;
use doiget_core::{ErrorCode, Ref};
use super::fetch::{cli_exit_code, CliExit, FetchHarness};
#[allow(clippy::print_stderr)]
fn print_err(args: std::fmt::Arguments<'_>) {
eprintln!("{args}");
}
pub async fn run(
input: String,
depth: Option<u32>,
total: Option<u32>,
per_paper: Option<u32>,
) -> Result<()> {
let ref_ = match Ref::parse(&input) {
Ok(r) => r,
Err(e) => {
print_err(format_args!(
"error[{}]: invalid ref: {e}",
ErrorCode::InvalidRef.as_wire()
));
return Err(anyhow::Error::new(CliExit(2)));
}
};
let doi = match &ref_ {
Ref::Doi(d) => d.clone(),
Ref::Arxiv(_) => {
print_err(format_args!(
"error: doiget graph requires a DOI seed; arXiv ids are not in OpenAlex's \
referenced_works keyspace"
));
return Err(anyhow::Error::new(CliExit(2)));
}
};
let harness = FetchHarness::from_env().context("building fetch harness")?;
if !harness.profile.metadata.openalex {
print_err(format_args!(
"error[{}]: doiget graph requires DOIGET_ENABLE_OPENALEX in env AND the binary \
built with `--features citation` (CapabilityProfile.metadata.openalex is false)",
ErrorCode::CapabilityDenied.as_wire()
));
return Err(anyhow::Error::new(CliExit(cli_exit_code(
ErrorCode::CapabilityDenied,
))));
}
let contact_email =
std::env::var("DOIGET_CONTACT_EMAIL").unwrap_or_else(|_| "doiget@localhost".to_string());
let source = if let Ok(base) = std::env::var("DOIGET_OPENALEX_BASE") {
if let Ok(url) = url::Url::parse(&base) {
OpenalexSource::with_base(url, contact_email)
} else {
OpenalexSource::new(contact_email)
}
} else {
OpenalexSource::new(contact_email)
};
harness
.log_session_start(Some(&input))
.context("logging session start")?;
let caps = GraphCaps {
depth: depth.map(|d| d as usize).unwrap_or(GraphCaps::MAX_DEPTH),
total: total.map(|t| t as usize).unwrap_or(GraphCaps::MAX_TOTAL),
per_paper: per_paper
.map(|p| p as usize)
.unwrap_or(GraphCaps::MAX_PER_PAPER),
};
let ctx = harness.fetch_context();
let outcome = expand(&doi, caps, &source, &harness.profile, &ctx).await;
let session_ok = outcome.is_ok();
harness.log_session_end(session_ok, Some(&input));
let graph = outcome.map_err(|e| match e {
GraphError::CapabilityDenied => {
print_err(format_args!(
"error[{}]: OpenAlex capability denied: set DOIGET_ENABLE_OPENALEX and \
rebuild with --features citation",
ErrorCode::CapabilityDenied.as_wire()
));
anyhow::Error::new(CliExit(cli_exit_code(ErrorCode::CapabilityDenied)))
}
GraphError::SeedNotIndexed => {
anyhow::anyhow!("seed DOI '{input}' is not indexed by OpenAlex")
}
other => anyhow::Error::new(other),
})?;
let json = serde_json::to_string_pretty(&graph)
.context("serializing GraphResult to JSON for stdout")?;
let stdout = std::io::stdout();
let mut out = stdout.lock();
writeln!(out, "{json}").context("writing graph JSON to stdout")?;
Ok(())
}