1mod execute;
7mod extract;
8mod refs;
9
10pub use execute::{execute_openapi, resolve_base_url};
11pub use extract::extract_openapi_commands;
12pub use refs::resolve_refs;
13
14use serde_json::Value;
15use std::fs;
16use std::path::Path;
17
18use crate::cache::{cache_key_for, load_cached, save_cache};
19use crate::error::{Error, Result};
20use crate::paths::DEFAULT_CACHE_TTL;
21
22pub fn load_openapi_spec_file(path: &Path) -> Result<Value> {
24 let raw = fs::read_to_string(path)?;
25 parse_spec(&raw)
26}
27
28pub fn parse_spec(raw: &str) -> Result<Value> {
29 let spec: Value = match serde_json::from_str(raw) {
30 Ok(v) => v,
31 Err(_) => serde_yaml::from_str(raw)?,
32 };
33 if !spec.is_object() || spec.get("paths").is_none() {
34 return Err(Error::runtime("spec must contain 'paths'"));
35 }
36 Ok(resolve_refs(spec))
37}
38
39pub fn load_openapi_spec_url(
41 url: &str,
42 auth_headers: &[(String, String)],
43 cache_key: Option<&str>,
44 ttl: u64,
45 refresh: bool,
46) -> Result<Value> {
47 let key = cache_key.map(str::to_string).unwrap_or_else(|| {
48 cache_key_for(&serde_json::json!({
49 "source": url,
50 "auth_headers": auth_headers,
51 }))
52 });
53 if !refresh {
54 if let Some(cached) = load_cached(&key, ttl)? {
55 return Ok(cached);
56 }
57 }
58
59 let client = reqwest::blocking::Client::builder()
60 .timeout(std::time::Duration::from_secs(30))
61 .build()
62 .map_err(|e| Error::runtime(e.to_string()))?;
63 let mut req = client.get(url);
64 for (k, v) in auth_headers {
65 req = req.header(k, v);
66 }
67 let resp = req.send().map_err(|e| Error::runtime(e.to_string()))?;
68 if !resp.status().is_success() {
69 return Err(Error::runtime(format!(
70 "Error {}: {}",
71 resp.status().as_u16(),
72 resp.text().unwrap_or_default()
73 )));
74 }
75 let raw = resp.text().map_err(|e| Error::runtime(e.to_string()))?;
76 let spec = parse_spec(&raw)?;
77 save_cache(&key, &spec)?;
78 Ok(spec)
79}
80
81pub fn load_openapi_spec(
82 source: &str,
83 auth_headers: &[(String, String)],
84 cache_key: Option<&str>,
85 ttl: Option<u64>,
86 refresh: bool,
87) -> Result<Value> {
88 let ttl = ttl.unwrap_or(DEFAULT_CACHE_TTL);
89 if source.starts_with("http://") || source.starts_with("https://") {
90 load_openapi_spec_url(source, auth_headers, cache_key, ttl, refresh)
91 } else {
92 load_openapi_spec_file(Path::new(source))
93 }
94}