openapi_snapshot/
fetch.rs

1use reqwest::blocking::Client;
2use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
3use serde_json::Value;
4use std::time::Duration;
5
6use crate::config::Config;
7use crate::errors::AppError;
8
9pub fn fetch_openapi(config: &Config) -> Result<Vec<u8>, AppError> {
10    let client = Client::builder()
11        .timeout(Duration::from_millis(config.timeout_ms))
12        .build()
13        .map_err(|err| AppError::Network(format!("client error: {err}")))?;
14
15    let headers = build_headers(&config.headers)?;
16    let response = client
17        .get(&config.url)
18        .headers(headers)
19        .send()
20        .map_err(|err| AppError::Network(format!("request failed: {err}")))?;
21
22    let status = response.status();
23    if !status.is_success() {
24        return Err(AppError::Network(format!(
25            "unexpected status: {status}"
26        )));
27    }
28
29    response
30        .bytes()
31        .map(|bytes| bytes.to_vec())
32        .map_err(|err| AppError::Network(format!("failed to read response: {err}")))
33}
34
35pub fn parse_json(bytes: &[u8]) -> Result<Value, AppError> {
36    serde_json::from_slice(bytes).map_err(|err| AppError::Json(format!("invalid JSON: {err}")))
37}
38
39fn build_headers(raw_headers: &[String]) -> Result<HeaderMap, AppError> {
40    let mut headers = HeaderMap::new();
41    for raw in raw_headers {
42        let (name, value) = parse_header(raw)?;
43        headers.insert(name, value);
44    }
45    Ok(headers)
46}
47
48fn parse_header(raw: &str) -> Result<(HeaderName, HeaderValue), AppError> {
49    let mut split = raw.splitn(2, ':');
50    let name = split
51        .next()
52        .map(str::trim)
53        .filter(|value| !value.is_empty())
54        .ok_or_else(|| AppError::Usage(format!("invalid header format: {raw}")))?;
55    let value = split
56        .next()
57        .map(str::trim)
58        .ok_or_else(|| AppError::Usage(format!("invalid header format: {raw}")))?;
59    let header_name = HeaderName::from_bytes(name.as_bytes())
60        .map_err(|_| AppError::Usage(format!("invalid header name: {name}")))?;
61    let header_value = HeaderValue::from_str(value)
62        .map_err(|_| AppError::Usage(format!("invalid header value for: {name}")))?;
63    Ok((header_name, header_value))
64}