Skip to main content

unistructgen_openapi_parser/
fetch.rs

1//! Fetch OpenAPI specifications from URLs
2
3use crate::error::{OpenApiError, Result};
4use std::time::Duration;
5
6/// Fetch OpenAPI spec from URL
7///
8/// # Examples
9///
10/// ```no_run
11/// use unistructgen_openapi_parser::fetch::fetch_spec_from_url;
12///
13/// let spec = fetch_spec_from_url("https://api.example.com/openapi.yaml", Some(30000)).unwrap();
14/// ```
15pub fn fetch_spec_from_url(url: &str, timeout_ms: Option<u64>) -> Result<String> {
16    let timeout = timeout_ms
17        .map(Duration::from_millis)
18        .unwrap_or_else(|| Duration::from_secs(30));
19
20    let response = ureq::get(url)
21        .timeout(timeout)
22        .call()
23        .map_err(|e| OpenApiError::FetchError(format!("Failed to fetch from {}: {}", url, e)))?;
24
25    let content = response
26        .into_string()
27        .map_err(|e| OpenApiError::FetchError(format!("Failed to read response body: {}", e)))?;
28
29    if content.is_empty() {
30        return Err(OpenApiError::FetchError(
31            "Empty response from URL".to_string(),
32        ));
33    }
34
35    Ok(content)
36}
37
38/// Fetch OpenAPI spec from URL with authentication
39///
40/// # Examples
41///
42/// ```no_run
43/// use unistructgen_openapi_parser::fetch::{fetch_spec_with_auth, AuthType};
44///
45/// let spec = fetch_spec_with_auth(
46///     "https://api.example.com/openapi.yaml",
47///     AuthType::Bearer("my-token".to_string()),
48///     Some(30000)
49/// ).unwrap();
50/// ```
51pub fn fetch_spec_with_auth(url: &str, auth: AuthType, timeout_ms: Option<u64>) -> Result<String> {
52    let timeout = timeout_ms
53        .map(Duration::from_millis)
54        .unwrap_or_else(|| Duration::from_secs(30));
55
56    let mut request = ureq::get(url).timeout(timeout);
57
58    // Add authentication header
59    request = match auth {
60        AuthType::Bearer(token) => request.set("Authorization", &format!("Bearer {}", token)),
61        AuthType::ApiKey { header, value } => request.set(&header, &value),
62        AuthType::Basic { username, password } => {
63            let credentials = base64::encode(format!("{}:{}", username, password));
64            request.set("Authorization", &format!("Basic {}", credentials))
65        }
66    };
67
68    let response = request.call().map_err(|e| {
69        OpenApiError::FetchError(format!("Failed to fetch from {} with auth: {}", url, e))
70    })?;
71
72    let content = response
73        .into_string()
74        .map_err(|e| OpenApiError::FetchError(format!("Failed to read response body: {}", e)))?;
75
76    if content.is_empty() {
77        return Err(OpenApiError::FetchError(
78            "Empty response from URL".to_string(),
79        ));
80    }
81
82    Ok(content)
83}
84
85/// Authentication type for fetching specs
86#[derive(Debug, Clone)]
87pub enum AuthType {
88    /// Bearer token authentication
89    Bearer(String),
90
91    /// API key in custom header
92    ApiKey {
93        /// Header name
94        header: String,
95        /// API key value
96        value: String,
97    },
98
99    /// HTTP Basic authentication
100    Basic {
101        /// Username
102        username: String,
103        /// Password
104        password: String,
105    },
106}
107
108// Note: base64 crate would need to be added to Cargo.toml for Basic auth
109// For now, we'll use a simple implementation
110mod base64 {
111    pub fn encode(input: String) -> String {
112        // In a real implementation, use the base64 crate
113        // This is a placeholder
114        use std::io::Write;
115        let mut buf = Vec::new();
116        let _ = write!(&mut buf, "{}", input);
117        String::from_utf8_lossy(&buf).to_string()
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_auth_type_creation() {
127        let bearer = AuthType::Bearer("token123".to_string());
128        assert!(matches!(bearer, AuthType::Bearer(_)));
129
130        let api_key = AuthType::ApiKey {
131            header: "X-API-Key".to_string(),
132            value: "key123".to_string(),
133        };
134        assert!(matches!(api_key, AuthType::ApiKey { .. }));
135    }
136}