romm_cli/tui/
openapi_sync.rs1use anyhow::{anyhow, Result};
5use serde_json::Value;
6use std::path::Path;
7
8use crate::client::RommClient;
9use crate::tui::openapi::EndpointRegistry;
10
11const EMBEDDED_OPENAPI_JSON: &str =
13 include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/openapi.json"));
14
15fn openapi_from_cwd() -> Option<String> {
16 let dir = std::env::current_dir().ok()?;
17 let p = dir.join("openapi.json");
18 if p.is_file() {
19 std::fs::read_to_string(p).ok()
20 } else {
21 None
22 }
23}
24
25pub fn parse_openapi_info_version(json: &str) -> Option<String> {
26 let v: Value = serde_json::from_str(json).ok()?;
27 v.get("info")?.get("version")?.as_str().map(String::from)
28}
29
30pub async fn sync_openapi_registry(
35 client: &RommClient,
36 cache_path: &Path,
37) -> Result<(EndpointRegistry, Option<String>)> {
38 let fetch_result = client.fetch_openapi_json().await;
39
40 let openapi_body = match fetch_result {
41 Ok(body) => {
42 let remote_ver = parse_openapi_info_version(&body);
43 let local_ver = std::fs::read_to_string(cache_path)
44 .ok()
45 .as_deref()
46 .and_then(parse_openapi_info_version);
47
48 let needs_write =
49 !cache_path.is_file() || local_ver.as_deref() != remote_ver.as_deref();
50
51 if needs_write {
52 if let Some(parent) = cache_path.parent() {
53 std::fs::create_dir_all(parent)
54 .map_err(|e| anyhow!("create OpenAPI cache dir: {e}"))?;
55 }
56 std::fs::write(cache_path, &body)
57 .map_err(|e| anyhow!("write OpenAPI cache {}: {e}", cache_path.display()))?;
58 tracing::info!(
59 "OpenAPI cache {} (version {:?})",
60 cache_path.display(),
61 remote_ver
62 );
63 }
64 body
65 }
66 Err(e) => {
67 if let Some(body) = openapi_from_cwd() {
68 tracing::warn!(
69 "Using ./openapi.json (could not fetch from server: {:#})",
70 e
71 );
72 body
73 } else if let Ok(cached) = std::fs::read_to_string(cache_path) {
74 tracing::warn!(
75 "Using cached OpenAPI at {} (server unreachable: {})",
76 cache_path.display(),
77 e
78 );
79 cached
80 } else {
81 tracing::warn!(
82 "Using bundled OpenAPI spec (server unreachable: {:#}). \
83 API browser paths match the build-time snapshot; connect to refresh from your server.",
84 e
85 );
86 EMBEDDED_OPENAPI_JSON.to_string()
87 }
88 }
89 };
90
91 let registry = EndpointRegistry::from_openapi_json(&openapi_body)
92 .map_err(|e| anyhow!("invalid OpenAPI document: {e}"))?;
93
94 let server_version = client.rom_server_version_from_heartbeat().await;
95
96 Ok((registry, server_version))
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn parses_info_version() {
105 let j = r#"{"openapi":"3.0.0","info":{"version":"1.2.3"},"paths":{}}"#;
106 assert_eq!(parse_openapi_info_version(j), Some("1.2.3".to_string()));
107 }
108
109 #[test]
110 fn embedded_openapi_json_parses() {
111 super::EndpointRegistry::from_openapi_json(EMBEDDED_OPENAPI_JSON)
112 .expect("bundled openapi.json");
113 }
114}