use anyhow::{anyhow, Result};
use serde_json::Value;
use std::path::Path;
use crate::client::RommClient;
use crate::tui::openapi::EndpointRegistry;
const EMBEDDED_OPENAPI_JSON: &str =
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/openapi.json"));
fn openapi_from_cwd() -> Option<String> {
let dir = std::env::current_dir().ok()?;
let p = dir.join("openapi.json");
if p.is_file() {
std::fs::read_to_string(p).ok()
} else {
None
}
}
pub fn parse_openapi_info_version(json: &str) -> Option<String> {
let v: Value = serde_json::from_str(json).ok()?;
v.get("info")?.get("version")?.as_str().map(String::from)
}
pub async fn sync_openapi_registry(
client: &RommClient,
cache_path: &Path,
) -> Result<(EndpointRegistry, Option<String>)> {
let fetch_result = client.fetch_openapi_json().await;
let openapi_body = match fetch_result {
Ok(body) => {
let remote_ver = parse_openapi_info_version(&body);
let local_ver = std::fs::read_to_string(cache_path)
.ok()
.as_deref()
.and_then(parse_openapi_info_version);
let needs_write =
!cache_path.is_file() || local_ver.as_deref() != remote_ver.as_deref();
if needs_write {
if let Some(parent) = cache_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| anyhow!("create OpenAPI cache dir: {e}"))?;
}
std::fs::write(cache_path, &body)
.map_err(|e| anyhow!("write OpenAPI cache {}: {e}", cache_path.display()))?;
tracing::info!(
"OpenAPI cache {} (version {:?})",
cache_path.display(),
remote_ver
);
}
body
}
Err(e) => {
if let Some(body) = openapi_from_cwd() {
tracing::warn!(
"Using ./openapi.json (could not fetch from server: {:#})",
e
);
body
} else if let Ok(cached) = std::fs::read_to_string(cache_path) {
tracing::warn!(
"Using cached OpenAPI at {} (server unreachable: {})",
cache_path.display(),
e
);
cached
} else {
tracing::warn!(
"Using bundled OpenAPI spec (server unreachable: {:#}). \
API browser paths match the build-time snapshot; connect to refresh from your server.",
e
);
EMBEDDED_OPENAPI_JSON.to_string()
}
}
};
let registry = EndpointRegistry::from_openapi_json(&openapi_body)
.map_err(|e| anyhow!("invalid OpenAPI document: {e}"))?;
let server_version = client.rom_server_version_from_heartbeat().await;
Ok((registry, server_version))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_info_version() {
let j = r#"{"openapi":"3.0.0","info":{"version":"1.2.3"},"paths":{}}"#;
assert_eq!(parse_openapi_info_version(j), Some("1.2.3".to_string()));
}
#[test]
fn embedded_openapi_json_parses() {
super::EndpointRegistry::from_openapi_json(EMBEDDED_OPENAPI_JSON)
.expect("bundled openapi.json");
}
}